Support creating EGLImage from VkImage

Bug: chromium:1264439
Change-Id: I520182143e748f25b44d0725f3f171b7b33a85d8
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3311131
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Peng Huang <penghuang@chromium.org>
diff --git a/extensions/EGL_ANGLE_vulkan_image.txt b/extensions/EGL_ANGLE_vulkan_image.txt
index 045767a..37b0ff5 100644
--- a/extensions/EGL_ANGLE_vulkan_image.txt
+++ b/extensions/EGL_ANGLE_vulkan_image.txt
@@ -20,7 +20,7 @@
 
 Version
 
-    Version 1, Nov 17, 2021
+    Version 2, Dec 10, 2021
 
 Number
 
@@ -51,7 +51,41 @@
 
 New Tokens
 
-    None
+    EGL_VULKAN_IMAGE_ANGLE 0x34D3
+    EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE 0x34D4
+    EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE 0x34D5
+
+Additions to Chapter 2 of the EGL 1.4 Specification (EGL Operation)
+
+    Add to section 2.5.1 "EGLImage Specification" (as defined by the
+    EGL_KHR_image_base specification), in the description of
+    eglCreateImageKHR:
+
+   "Values accepted for <target> are listed in Table aaa, below.
+
+      +----------------------------+-----------------------------------------+
+      |  <target>                  |  Notes                                  |
+      +----------------------------+-----------------------------------------+
+      |  EGL_VULKAN_IMAGE_ANGLE    |  Used for VkImage objects               |
+      +----------------------------+-----------------------------------------+
+       Table aaa.  Legal values for eglCreateImageKHR <target> parameter
+
+    ...
+
+    If <target> is EGL_VULKAN_IMAGE_ANGLE, <dpy> must be a valid display, <ctx>
+    must be EGL_NO_CONTEXT, <buffer> must be a pointer to a valid VkImage
+    (cast into the type EGLClientBuffer), the VkImage must be created with the
+    same VkDevice used by GL implementation and attributes other than
+    EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE or
+    EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE are ignored.
+
+    EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE and
+    EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE must be specified. They contain
+    hi 32bits and lo 32bits of a pointer to the memory stores a valid
+    VkImageCreateInfo structure. The GL implementation will get all necessary
+    info of the VkImage from it. All supported structures in the pNext structure
+    chain will be parsed, not supported structures will be ignored.
+
 
 Additions to the EGL 1.4 Specification:
 
@@ -74,4 +108,6 @@
 
 Revision History
 
-    Version 1, 2021/11/17 - first draft.
\ No newline at end of file
+    Version 1, 2021/11/17 - first draft.
+
+    Version 2, 2021/12/10 - add support for creating EGLImageKHR from VkImage.
\ No newline at end of file
diff --git a/include/EGL/eglext_angle.h b/include/EGL/eglext_angle.h
index 74f3b9d..ff15684 100644
--- a/include/EGL/eglext_angle.h
+++ b/include/EGL/eglext_angle.h
@@ -376,6 +376,9 @@
 
 #ifndef EGL_ANGLE_vulkan_image
 #define EGL_ANGLE_vulkan_image
+#define EGL_VULKAN_IMAGE_ANGLE 0x34D3
+#define EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE 0x34D4
+#define EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE 0x34D5
 typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTVKIMAGEANGLEPROC)(EGLDisplay dpy, EGLImage image, void* vk_image, void* vk_image_create_info);
 #endif /* EGL_ANGLE_vulkan_image */
 
diff --git a/src/common/utilities.cpp b/src/common/utilities.cpp
index bf15015..ebe282c 100644
--- a/src/common/utilities.cpp
+++ b/src/common/utilities.cpp
@@ -1275,6 +1275,7 @@
         case EGL_D3D11_TEXTURE_ANGLE:
         case EGL_LINUX_DMA_BUF_EXT:
         case EGL_METAL_TEXTURE_ANGLE:
+        case EGL_VULKAN_IMAGE_ANGLE:
             return true;
 
         default:
diff --git a/src/libANGLE/renderer/vulkan/BUILD.gn b/src/libANGLE/renderer/vulkan/BUILD.gn
index 980dd46..f0ceccc 100644
--- a/src/libANGLE/renderer/vulkan/BUILD.gn
+++ b/src/libANGLE/renderer/vulkan/BUILD.gn
@@ -84,6 +84,8 @@
   "UtilsVk.h",
   "VertexArrayVk.cpp",
   "VertexArrayVk.h",
+  "VkImageImageSiblingVk.cpp",
+  "VkImageImageSiblingVk.h",
   "VulkanSecondaryCommandBuffer.cpp",
   "VulkanSecondaryCommandBuffer.h",
   "android/vk_android_utils.cpp",
diff --git a/src/libANGLE/renderer/vulkan/DisplayVk.cpp b/src/libANGLE/renderer/vulkan/DisplayVk.cpp
index 9158d5c..4adf94a 100644
--- a/src/libANGLE/renderer/vulkan/DisplayVk.cpp
+++ b/src/libANGLE/renderer/vulkan/DisplayVk.cpp
@@ -18,6 +18,7 @@
 #include "libANGLE/renderer/vulkan/RendererVk.h"
 #include "libANGLE/renderer/vulkan/SurfaceVk.h"
 #include "libANGLE/renderer/vulkan/SyncVk.h"
+#include "libANGLE/renderer/vulkan/VkImageImageSiblingVk.h"
 #include "libANGLE/trace.h"
 
 namespace rx
@@ -202,6 +203,54 @@
     return mRenderer->getMaxConformantESVersion();
 }
 
+egl::Error DisplayVk::validateImageClientBuffer(const gl::Context *context,
+                                                EGLenum target,
+                                                EGLClientBuffer clientBuffer,
+                                                const egl::AttributeMap &attribs) const
+{
+    switch (target)
+    {
+        case EGL_VULKAN_IMAGE_ANGLE:
+        {
+            VkImage *vkImage = reinterpret_cast<VkImage *>(clientBuffer);
+            if (!vkImage || *vkImage == VK_NULL_HANDLE)
+            {
+                return egl::EglBadParameter() << "clientBuffer is invalid.";
+            }
+
+            uint64_t hi = static_cast<uint64_t>(attribs.get(EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE));
+            uint64_t lo = static_cast<uint64_t>(attribs.get(EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE));
+            uint64_t info = ((hi & 0xffffffff) << 32) | (lo & 0xffffffff);
+            if (reinterpret_cast<const VkImageCreateInfo *>(info)->sType !=
+                VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO)
+            {
+                return egl::EglBadParameter()
+                       << "EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE and "
+                          "EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE are not pointing to a "
+                          "valid VkImageCreateInfo structure.";
+            }
+
+            return egl::NoError();
+        }
+        default:
+            return DisplayImpl::validateImageClientBuffer(context, target, clientBuffer, attribs);
+    }
+}
+
+ExternalImageSiblingImpl *DisplayVk::createExternalImageSibling(const gl::Context *context,
+                                                                EGLenum target,
+                                                                EGLClientBuffer buffer,
+                                                                const egl::AttributeMap &attribs)
+{
+    switch (target)
+    {
+        case EGL_VULKAN_IMAGE_ANGLE:
+            return new VkImageImageSiblingVk(buffer, attribs);
+        default:
+            return DisplayImpl::createExternalImageSibling(context, target, buffer, attribs);
+    }
+}
+
 void DisplayVk::generateExtensions(egl::DisplayExtensions *outExtensions) const
 {
     outExtensions->createContextRobustness    = getRenderer()->getNativeExtensions().robustnessEXT;
diff --git a/src/libANGLE/renderer/vulkan/DisplayVk.h b/src/libANGLE/renderer/vulkan/DisplayVk.h
index 7fca3f5..0a1305e 100644
--- a/src/libANGLE/renderer/vulkan/DisplayVk.h
+++ b/src/libANGLE/renderer/vulkan/DisplayVk.h
@@ -117,6 +117,14 @@
     gl::Version getMaxSupportedESVersion() const override;
     gl::Version getMaxConformantESVersion() const override;
 
+    egl::Error validateImageClientBuffer(const gl::Context *context,
+                                         EGLenum target,
+                                         EGLClientBuffer clientBuffer,
+                                         const egl::AttributeMap &attribs) const override;
+    ExternalImageSiblingImpl *createExternalImageSibling(const gl::Context *context,
+                                                         EGLenum target,
+                                                         EGLClientBuffer buffer,
+                                                         const egl::AttributeMap &attribs) override;
     virtual const char *getWSIExtension() const = 0;
     virtual const char *getWSILayer() const;
     virtual bool isUsingSwapchain() const;
diff --git a/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp b/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp
new file mode 100644
index 0000000..bf16c6c
--- /dev/null
+++ b/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp
@@ -0,0 +1,126 @@
+//
+// 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.
+//
+
+// VkImageImageSiblingVk.cpp: Implements VkImageImageSiblingVk.
+
+#include "libANGLE/renderer/vulkan/VkImageImageSiblingVk.h"
+
+#include "libANGLE/Display.h"
+#include "libANGLE/renderer/vulkan/DisplayVk.h"
+#include "libANGLE/renderer/vulkan/RendererVk.h"
+
+namespace rx
+{
+
+VkImageImageSiblingVk::VkImageImageSiblingVk(EGLClientBuffer buffer,
+                                             const egl::AttributeMap &attribs)
+{
+    mVkImage.setHandle(*reinterpret_cast<VkImage *>(buffer));
+
+    ASSERT(attribs.contains(EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE));
+    ASSERT(attribs.contains(EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE));
+    uint64_t hi = static_cast<uint64_t>(attribs.get(EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE));
+    uint64_t lo = static_cast<uint64_t>(attribs.get(EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE));
+    const VkImageCreateInfo *info =
+        reinterpret_cast<const VkImageCreateInfo *>((hi << 32) | (lo & 0xffffffff));
+    ASSERT(info->sType == VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);
+    mVkImageInfo = *info;
+    // TODO(penghuang): support extensions.
+    mVkImageInfo.pNext = nullptr;
+}
+
+VkImageImageSiblingVk::~VkImageImageSiblingVk() = default;
+
+egl::Error VkImageImageSiblingVk::initialize(const egl::Display *display)
+{
+    DisplayVk *displayVk = vk::GetImpl(display);
+    return angle::ToEGL(initImpl(displayVk), displayVk, EGL_BAD_PARAMETER);
+}
+
+angle::Result VkImageImageSiblingVk::initImpl(DisplayVk *displayVk)
+{
+    RendererVk *renderer = displayVk->getRenderer();
+
+    const angle::FormatID formatID = vk::GetFormatIDFromVkFormat(mVkImageInfo.format);
+    ANGLE_VK_CHECK(displayVk, formatID != angle::FormatID::NONE, VK_ERROR_FORMAT_NOT_SUPPORTED);
+
+    const vk::Format &vkFormat             = renderer->getFormat(formatID);
+    const angle::FormatID intendedFormatID = vkFormat.getIntendedFormatID();
+    const vk::ImageAccess imageAccess =
+        isRenderable(nullptr) ? vk::ImageAccess::Renderable : vk::ImageAccess::SampleOnly;
+    const angle::FormatID actualImageFormatID = vkFormat.getActualImageFormatID(imageAccess);
+    const angle::Format &format               = angle::Format::Get(actualImageFormatID);
+    mFormat                                   = gl::Format(format.glInternalFormat);
+
+    // Create the image
+    mImage                              = new vk::ImageHelper();
+    constexpr bool kIsRobustInitEnabled = false;
+    mImage->init2DWeakReference(displayVk, mVkImage.release(), getSize(), false, intendedFormatID,
+                                actualImageFormatID, 1, kIsRobustInitEnabled);
+
+    return angle::Result::Continue;
+}
+
+void VkImageImageSiblingVk::onDestroy(const egl::Display *display)
+{
+    ASSERT(mImage == nullptr);
+}
+
+gl::Format VkImageImageSiblingVk::getFormat() const
+{
+    return mFormat;
+}
+
+bool VkImageImageSiblingVk::isRenderable(const gl::Context *context) const
+{
+    return mVkImageInfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+}
+
+bool VkImageImageSiblingVk::isTexturable(const gl::Context *context) const
+{
+    return mVkImageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT;
+}
+
+bool VkImageImageSiblingVk::isYUV() const
+{
+    return false;
+}
+
+bool VkImageImageSiblingVk::hasProtectedContent() const
+{
+    return false;
+}
+
+gl::Extents VkImageImageSiblingVk::getSize() const
+{
+    return gl::Extents(mVkImageInfo.extent.width, mVkImageInfo.extent.height,
+                       mVkImageInfo.extent.depth);
+}
+
+size_t VkImageImageSiblingVk::getSamples() const
+{
+    return 0;
+}
+
+// ExternalImageSiblingVk interface
+vk::ImageHelper *VkImageImageSiblingVk::getImage() const
+{
+    return mImage;
+}
+
+void VkImageImageSiblingVk::release(RendererVk *renderer)
+{
+    if (mImage != nullptr)
+    {
+        // TODO: Handle the case where the EGLImage is used in two contexts not in the same share
+        // group.  https://issuetracker.google.com/169868803
+        mImage->resetImageWeakReference();
+        mImage->destroy(renderer);
+        SafeDelete(mImage);
+    }
+}
+
+}  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.h b/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.h
new file mode 100644
index 0000000..571eac6
--- /dev/null
+++ b/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.h
@@ -0,0 +1,52 @@
+//
+// 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.
+//
+
+// VkImageImageSiblingVk.h: Defines the VkImageImageSiblingVk to wrap
+// EGL images created from VkImage.
+
+#ifndef LIBANGLE_RENDERER_VULKAN_VKIMAGEIMAGESIBLINGVK_H_
+#define LIBANGLE_RENDERER_VULKAN_VKIMAGEIMAGESIBLINGVK_H_
+
+#include "libANGLE/renderer/vulkan/ImageVk.h"
+
+namespace rx
+{
+
+class VkImageImageSiblingVk final : public ExternalImageSiblingVk
+{
+  public:
+    VkImageImageSiblingVk(EGLClientBuffer buffer, const egl::AttributeMap &attribs);
+    ~VkImageImageSiblingVk() override;
+
+    egl::Error initialize(const egl::Display *display) override;
+    void onDestroy(const egl::Display *display) override;
+
+    // ExternalImageSiblingImpl interface
+    gl::Format getFormat() const override;
+    bool isRenderable(const gl::Context *context) const override;
+    bool isTexturable(const gl::Context *context) const override;
+    bool isYUV() const override;
+    bool hasProtectedContent() const override;
+    gl::Extents getSize() const override;
+    size_t getSamples() const override;
+
+    // ExternalImageSiblingVk interface
+    vk::ImageHelper *getImage() const override;
+
+    void release(RendererVk *renderer) override;
+
+  private:
+    angle::Result initImpl(DisplayVk *displayVk);
+
+    vk::Image mVkImage;
+    VkImageCreateInfo mVkImageInfo;
+    gl::Format mFormat{GL_NONE};
+    vk::ImageHelper *mImage = nullptr;
+};
+
+}  // namespace rx
+
+#endif  // LIBANGLE_RENDERER_VULKAN_VKIMAGEIMAGESIBLINGVK_H_
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index b0ec858..f7c1791 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -4793,9 +4793,10 @@
     mActualFormatID     = actualFormatID;
     mSamples            = std::max(samples, 1);
     mImageSerial        = context->getRenderer()->getResourceSerialFactory().generateImageSerial();
-    mCurrentLayout      = ImageLayout::Undefined;
-    mLayerCount         = 1;
-    mLevelCount         = 1;
+    mCurrentQueueFamilyIndex = context->getRenderer()->getQueueFamilyIndex();
+    mCurrentLayout           = ImageLayout::Undefined;
+    mLayerCount              = 1;
+    mLevelCount              = 1;
 
     mImage.setHandle(handle);
 
diff --git a/src/libANGLE/validationEGL.cpp b/src/libANGLE/validationEGL.cpp
index ce4f937..c854cb0 100644
--- a/src/libANGLE/validationEGL.cpp
+++ b/src/libANGLE/validationEGL.cpp
@@ -3366,6 +3366,17 @@
                 }
                 break;
 
+            case EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE:
+            case EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE:
+                if (!displayExtensions.vulkanImageANGLE)
+                {
+                    val->setError(EGL_BAD_ATTRIBUTE,
+                                  "Attribute EGL_VULKAN_IMAGE_CREATE_INFO_{HI,LO}_ANGLE require "
+                                  "extension EGL_ANGLE_vulkan_image.");
+                    return false;
+                }
+                break;
+
             default:
                 val->setError(EGL_BAD_PARAMETER, "invalid attribute: 0x%04" PRIxPTR "X", attribute);
                 return false;
@@ -3739,6 +3750,42 @@
                 display->validateImageClientBuffer(context, target, buffer, attributes),
                 val->entryPoint, val->labeledObject, false);
             break;
+        case EGL_VULKAN_IMAGE_ANGLE:
+            if (!displayExtensions.vulkanImageANGLE)
+            {
+                val->setError(EGL_BAD_PARAMETER, "EGL_ANGLE_vulkan_image not supported.");
+                return false;
+            }
+
+            if (context != nullptr)
+            {
+                val->setError(EGL_BAD_CONTEXT, "ctx must be EGL_NO_CONTEXT.");
+                return false;
+            }
+
+            {
+                const EGLenum kRequiredParameters[] = {
+                    EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE,
+                    EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE,
+                };
+                for (EGLenum requiredParameter : kRequiredParameters)
+                {
+                    if (!attributes.contains(requiredParameter))
+                    {
+                        val->setError(EGL_BAD_PARAMETER,
+                                      "Missing required parameter 0x%X for image target "
+                                      "EGL_VULKAN_IMAGE_ANGLE.",
+                                      requiredParameter);
+                        return false;
+                    }
+                }
+            }
+
+            ANGLE_EGL_TRY_RETURN(
+                val->eglThread,
+                display->validateImageClientBuffer(context, target, buffer, attributes),
+                val->entryPoint, val->labeledObject, false);
+            break;
         default:
             val->setError(EGL_BAD_PARAMETER, "invalid target: 0x%X", target);
             return false;
diff --git a/src/tests/capture_replay_tests/capture_replay_expectations.txt b/src/tests/capture_replay_tests/capture_replay_expectations.txt
index 0e2aec7..dce32d8 100644
--- a/src/tests/capture_replay_tests/capture_replay_expectations.txt
+++ b/src/tests/capture_replay_tests/capture_replay_expectations.txt
@@ -216,4 +216,4 @@
 6663 : Texture2DTest.UploadThenFSThenVS/* = NOT_RUN
 6663 : Texture2DTest.UploadThenFSThenVSThenNewRPThenFS/*  = NOT_RUN
 
-6741 : VulkanImageTest.PixelTest* = SKIP_FOR_CAPTURE
+6741 : VulkanImageTest.* = SKIP_FOR_CAPTURE
diff --git a/src/tests/gl_tests/VulkanImageTest.cpp b/src/tests/gl_tests/VulkanImageTest.cpp
index f378582..a58f850 100644
--- a/src/tests/gl_tests/VulkanImageTest.cpp
+++ b/src/tests/gl_tests/VulkanImageTest.cpp
@@ -22,11 +22,30 @@
 
 using VulkanImageTest = ANGLETest;
 
+// 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());
 
-    EXPECT_TRUE(IsEGLClientExtensionEnabled("EGL_EXT_device_query"));
     EGLWindow *window  = getEGLWindow();
     EGLDisplay display = window->getDisplay();
 
@@ -35,7 +54,6 @@
 
     EGLDeviceEXT device = reinterpret_cast<EGLDeviceEXT>(result);
     EXPECT_NE(EGL_NO_DEVICE_EXT, device);
-    EXPECT_TRUE(IsEGLDeviceExtensionEnabled(device, "EGL_ANGLE_device_vulkan"));
 
     EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(device, EGL_VULKAN_INSTANCE_ANGLE, &result));
     VkInstance instance = reinterpret_cast<VkInstance>(result);
@@ -95,13 +113,9 @@
 
 TEST_P(VulkanImageTest, ExportVKImage)
 {
-    ANGLE_SKIP_TEST_IF(!IsVulkan());
-
     EGLWindow *window  = getEGLWindow();
     EGLDisplay display = window->getDisplay();
-
-    EXPECT_TRUE(IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
-    EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_vulkan_image"));
+    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
 
     GLTexture texture;
     glBindTexture(GL_TEXTURE_2D, texture);
@@ -133,18 +147,16 @@
     EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
 }
 
+// Check pixels after glTexImage2D
 TEST_P(VulkanImageTest, PixelTestTexImage2D)
 {
-    ANGLE_SKIP_TEST_IF(!IsVulkan());
-
-    VulkanHelper helper;
-    helper.initializeFromANGLE();
-
     EGLWindow *window  = getEGLWindow();
     EGLDisplay display = window->getDisplay();
 
-    EXPECT_TRUE(IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
-    EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_vulkan_image"));
+    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
+
+    VulkanHelper helper;
+    helper.initializeFromANGLE();
 
     constexpr GLuint kColor = 0xafbfcfdf;
 
@@ -188,16 +200,17 @@
     EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
 }
 
+// Check pixels after glClear
 TEST_P(VulkanImageTest, PixelTestClear)
 {
-    ANGLE_SKIP_TEST_IF(!IsVulkan());
+    EGLWindow *window  = getEGLWindow();
+    EGLDisplay display = window->getDisplay();
+
+    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
 
     VulkanHelper helper;
     helper.initializeFromANGLE();
 
-    EGLWindow *window  = getEGLWindow();
-    EGLDisplay display = window->getDisplay();
-
     GLTexture texture;
     glBindTexture(GL_TEXTURE_2D, texture);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
@@ -256,16 +269,17 @@
     glBindFramebuffer(GL_FRAMEBUFFER, 0);
 }
 
+// Check pixels after GL draw.
 TEST_P(VulkanImageTest, PixelTestDrawQuad)
 {
-    ANGLE_SKIP_TEST_IF(!IsVulkan());
+    EGLWindow *window  = getEGLWindow();
+    EGLDisplay display = window->getDisplay();
+
+    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
 
     VulkanHelper helper;
     helper.initializeFromANGLE();
 
-    EGLWindow *window  = getEGLWindow();
-    EGLDisplay display = window->getDisplay();
-
     GLTexture texture;
     glBindTexture(GL_TEXTURE_2D, texture);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
@@ -314,6 +328,99 @@
     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], static_cast<GLenum>(GL_LAYOUT_TRANSFER_DST_EXT));
+
+    std::vector<GLuint> pixels(kWidth * kHeight);
+    helper.readPixels(vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 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], static_cast<GLenum>(GL_LAYOUT_TRANSFER_DST_EXT));
+
+    helper.readPixels(vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 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);
+}
 // 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(VulkanImageTest);
diff --git a/src/tests/test_utils/VulkanHelper.cpp b/src/tests/test_utils/VulkanHelper.cpp
index fdae4bd..c87ced5 100644
--- a/src/tests/test_utils/VulkanHelper.cpp
+++ b/src/tests/test_utils/VulkanHelper.cpp
@@ -461,6 +461,80 @@
     ASSERT(!mHasExternalSemaphoreFuchsia || vkGetSemaphoreZirconHandleFUCHSIA);
 }
 
+VkResult VulkanHelper::createImage2D(VkFormat format,
+                                     VkImageCreateFlags createFlags,
+                                     VkImageUsageFlags usageFlags,
+                                     VkExtent3D extent,
+                                     VkImage *imageOut,
+                                     VkDeviceMemory *deviceMemoryOut,
+                                     VkDeviceSize *deviceMemorySizeOut,
+                                     VkImageCreateInfo *imageCreateInfoOut)
+{
+    VkImageCreateInfo imageCreateInfo = {
+        /* .sType = */ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+        /* .pNext = */ nullptr,
+        /* .flags = */ createFlags,
+        /* .imageType = */ VK_IMAGE_TYPE_2D,
+        /* .format = */ format,
+        /* .extent = */ extent,
+        /* .mipLevels = */ 1,
+        /* .arrayLayers = */ 1,
+        /* .samples = */ VK_SAMPLE_COUNT_1_BIT,
+        /* .tiling = */ VK_IMAGE_TILING_OPTIMAL,
+        /* .usage = */ usageFlags,
+        /* .sharingMode = */ VK_SHARING_MODE_EXCLUSIVE,
+        /* .queueFamilyIndexCount = */ 0,
+        /* .pQueueFamilyIndices = */ nullptr,
+        /* initialLayout = */ VK_IMAGE_LAYOUT_UNDEFINED,
+    };
+
+    VkImage image   = VK_NULL_HANDLE;
+    VkResult result = vkCreateImage(mDevice, &imageCreateInfo, nullptr, &image);
+    if (result != VK_SUCCESS)
+    {
+        return result;
+    }
+
+    VkMemoryPropertyFlags requestedMemoryPropertyFlags = 0;
+    VkMemoryRequirements memoryRequirements;
+    vkGetImageMemoryRequirements(mDevice, image, &memoryRequirements);
+    uint32_t memoryTypeIndex = FindMemoryType(mMemoryProperties, memoryRequirements.memoryTypeBits,
+                                              requestedMemoryPropertyFlags);
+    ASSERT(memoryTypeIndex != UINT32_MAX);
+    VkDeviceSize deviceMemorySize = memoryRequirements.size;
+
+    VkMemoryAllocateInfo memoryAllocateInfo = {
+        /* .sType = */ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+        /* .pNext = */ nullptr,
+        /* .allocationSize = */ deviceMemorySize,
+        /* .memoryTypeIndex = */ memoryTypeIndex,
+    };
+
+    VkDeviceMemory deviceMemory = VK_NULL_HANDLE;
+    result = vkAllocateMemory(mDevice, &memoryAllocateInfo, nullptr, &deviceMemory);
+    if (result != VK_SUCCESS)
+    {
+        vkDestroyImage(mDevice, image, nullptr);
+        return result;
+    }
+
+    VkDeviceSize memoryOffset = 0;
+    result                    = vkBindImageMemory(mDevice, image, deviceMemory, memoryOffset);
+    if (result != VK_SUCCESS)
+    {
+        vkFreeMemory(mDevice, deviceMemory, nullptr);
+        vkDestroyImage(mDevice, image, nullptr);
+        return result;
+    }
+
+    *imageOut            = image;
+    *deviceMemoryOut     = deviceMemory;
+    *deviceMemorySizeOut = deviceMemorySize;
+    *imageCreateInfoOut  = imageCreateInfo;
+
+    return VK_SUCCESS;
+}
+
 bool VulkanHelper::canCreateImageExternal(VkFormat format,
                                           VkImageType type,
                                           VkImageTiling tiling,
diff --git a/src/tests/test_utils/VulkanHelper.h b/src/tests/test_utils/VulkanHelper.h
index 4e3c26a..9177ab1 100644
--- a/src/tests/test_utils/VulkanHelper.h
+++ b/src/tests/test_utils/VulkanHelper.h
@@ -28,6 +28,14 @@
     VkDevice getDevice() const { return mDevice; }
     VkQueue getGraphicsQueue() const { return mGraphicsQueue; }
 
+    VkResult createImage2D(VkFormat format,
+                           VkImageCreateFlags createFlags,
+                           VkImageUsageFlags usageFlags,
+                           VkExtent3D extent,
+                           VkImage *imageOut,
+                           VkDeviceMemory *deviceMemoryOut,
+                           VkDeviceSize *deviceMemorySizeOut,
+                           VkImageCreateInfo *imageCreateInfoOut);
     bool canCreateImageExternal(VkFormat format,
                                 VkImageType type,
                                 VkImageTiling tiling,