Vulkan: Implement glImportSemaphoreFdEXT

Allow importing file descriptors into semaphores on linux. This can be
used to synchronize ANGLE's GL renderer with respect to vulkan
composition in chromium.

Bug: angleproject:3289

Change-Id: I04ba3bbb2e343baa000ff89c21c03ca36163a713
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1623812
Commit-Queue: Michael Spang <spang@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 70654d7..88af5a2 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -7265,7 +7265,9 @@
 
 void Context::importSemaphoreFd(GLuint semaphore, HandleType handleType, GLint fd)
 {
-    UNIMPLEMENTED();
+    Semaphore *semaphoreObject = getSemaphore(semaphore);
+    ASSERT(semaphoreObject != nullptr);
+    ANGLE_CONTEXT_TRY(semaphoreObject->importFd(this, handleType, fd));
 }
 
 void Context::eGLImageTargetTexture2D(TextureType target, GLeglImageOES image)
diff --git a/src/libANGLE/Semaphore.cpp b/src/libANGLE/Semaphore.cpp
index 10bcf02..a224bec 100644
--- a/src/libANGLE/Semaphore.cpp
+++ b/src/libANGLE/Semaphore.cpp
@@ -25,4 +25,9 @@
     mImplementation->onDestroy(context);
 }
 
+angle::Result Semaphore::importFd(Context *context, HandleType handleType, GLint fd)
+{
+    return mImplementation->importFd(context, handleType, fd);
+}
+
 }  // namespace gl
diff --git a/src/libANGLE/Semaphore.h b/src/libANGLE/Semaphore.h
index 2e65d2e..db2d25a 100644
--- a/src/libANGLE/Semaphore.h
+++ b/src/libANGLE/Semaphore.h
@@ -11,7 +11,9 @@
 #include <memory>
 
 #include "angle_gl.h"
+#include "common/PackedEnums.h"
 #include "common/angleutils.h"
+#include "libANGLE/Error.h"
 #include "libANGLE/RefCountObject.h"
 
 namespace rx
@@ -34,6 +36,8 @@
 
     rx::SemaphoreImpl *getImplementation() const { return mImplementation.get(); }
 
+    angle::Result importFd(Context *context, HandleType handleType, GLint fd);
+
   private:
     std::unique_ptr<rx::SemaphoreImpl> mImplementation;
 };
diff --git a/src/libANGLE/renderer/SemaphoreImpl.h b/src/libANGLE/renderer/SemaphoreImpl.h
index 813524c..2ebff59 100644
--- a/src/libANGLE/renderer/SemaphoreImpl.h
+++ b/src/libANGLE/renderer/SemaphoreImpl.h
@@ -28,6 +28,8 @@
     virtual ~SemaphoreImpl() {}
 
     virtual void onDestroy(const gl::Context *context) = 0;
+
+    virtual angle::Result importFd(gl::Context *context, gl::HandleType handleType, GLint fd) = 0;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index d8388a3..0b84b8d 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -1012,6 +1012,7 @@
     if (getFeatures().supportsExternalSemaphoreFd.enabled)
     {
         enabledDeviceExtensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
+        InitExternalSemaphoreFdFunctions(mInstance);
     }
 
     if (getFeatures().supportsExternalSemaphoreFd.enabled)
diff --git a/src/libANGLE/renderer/vulkan/SemaphoreVk.cpp b/src/libANGLE/renderer/vulkan/SemaphoreVk.cpp
index 16dacc3..90bdf6b 100644
--- a/src/libANGLE/renderer/vulkan/SemaphoreVk.cpp
+++ b/src/libANGLE/renderer/vulkan/SemaphoreVk.cpp
@@ -9,6 +9,8 @@
 
 #include "common/debug.h"
 #include "libANGLE/Context.h"
+#include "libANGLE/renderer/vulkan/ContextVk.h"
+#include "libANGLE/renderer/vulkan/RendererVk.h"
 
 namespace rx
 {
@@ -17,6 +19,50 @@
 
 SemaphoreVk::~SemaphoreVk() = default;
 
-void SemaphoreVk::onDestroy(const gl::Context *context) {}
+void SemaphoreVk::onDestroy(const gl::Context *context)
+{
+    ContextVk *contextVk = vk::GetImpl(context);
+    RendererVk *renderer = contextVk->getRenderer();
+
+    Serial currentSerial = renderer->getCurrentQueueSerial();
+    renderer->releaseObject(currentSerial, &mSemaphore);
+}
+
+angle::Result SemaphoreVk::importFd(gl::Context *context, gl::HandleType handleType, GLint fd)
+{
+    switch (handleType)
+    {
+        case gl::HandleType::OpaqueFd:
+            return importOpaqueFd(context, fd);
+
+        default:
+            ANGLE_VK_UNREACHABLE(vk::GetImpl(context));
+            return angle::Result::Stop;
+    }
+}
+
+angle::Result SemaphoreVk::importOpaqueFd(gl::Context *context, GLint fd)
+{
+    ContextVk *contextVk = vk::GetImpl(context);
+    RendererVk *renderer = contextVk->getRenderer();
+
+    if (!mSemaphore.valid())
+    {
+        mSemaphore.init(renderer->getDevice());
+    }
+
+    ASSERT(mSemaphore.valid());
+
+    VkImportSemaphoreFdInfoKHR importSemaphoreFdInfo = {};
+    importSemaphoreFdInfo.sType      = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
+    importSemaphoreFdInfo.semaphore  = mSemaphore.getHandle();
+    importSemaphoreFdInfo.flags      = 0;
+    importSemaphoreFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
+    importSemaphoreFdInfo.fd         = fd;
+
+    ANGLE_VK_TRY(contextVk, vkImportSemaphoreFdKHR(renderer->getDevice(), &importSemaphoreFdInfo));
+
+    return angle::Result::Continue;
+}
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/SemaphoreVk.h b/src/libANGLE/renderer/vulkan/SemaphoreVk.h
index edbf6f9..a29b792 100644
--- a/src/libANGLE/renderer/vulkan/SemaphoreVk.h
+++ b/src/libANGLE/renderer/vulkan/SemaphoreVk.h
@@ -22,6 +22,13 @@
     ~SemaphoreVk() override;
 
     void onDestroy(const gl::Context *context) override;
+
+    angle::Result importFd(gl::Context *context, gl::HandleType handleType, GLint fd) override;
+
+  private:
+    angle::Result importOpaqueFd(gl::Context *context, GLint fd);
+
+    vk::Semaphore mSemaphore;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.cpp b/src/libANGLE/renderer/vulkan/vk_utils.cpp
index 01d88d9..e266e3e 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_utils.cpp
@@ -522,6 +522,9 @@
 // VK_KHR_get_physical_device_properties2
 PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR = nullptr;
 
+// VK_KHR_external_semaphore_fd
+PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr;
+
 #if defined(ANGLE_PLATFORM_FUCHSIA)
 // VK_FUCHSIA_imagepipe_surface
 PFN_vkCreateImagePipeSurfaceFUCHSIA vkCreateImagePipeSurfaceFUCHSIA = nullptr;
@@ -572,6 +575,11 @@
 }
 #endif
 
+void InitExternalSemaphoreFdFunctions(VkInstance instance)
+{
+    GET_FUNC(vkImportSemaphoreFdKHR);
+}
+
 #undef GET_FUNC
 
 namespace gl_vk
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.h b/src/libANGLE/renderer/vulkan/vk_utils.h
index f3fae2e..707290f 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_utils.h
@@ -494,6 +494,9 @@
 // VK_KHR_get_physical_device_properties2
 extern PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR;
 
+// VK_KHR_external_semaphore_fd
+extern PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR;
+
 // Lazily load entry points for each extension as necessary.
 void InitDebugUtilsEXTFunctions(VkInstance instance);
 void InitDebugReportEXTFunctions(VkInstance instance);
@@ -512,6 +515,8 @@
 void InitExternalMemoryHardwareBufferANDROIDFunctions(VkInstance instance);
 #endif
 
+void InitExternalSemaphoreFdFunctions(VkInstance instance);
+
 namespace gl_vk
 {
 VkRect2D GetRect(const gl::Rectangle &source);
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index da61ed44..b98de66 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -3374,8 +3374,16 @@
         return false;
     }
 
-    UNIMPLEMENTED();
-    return false;
+    switch (handleType)
+    {
+        case HandleType::OpaqueFd:
+            break;
+        default:
+            context->validationError(GL_INVALID_ENUM, kInvalidHandleType);
+            return false;
+    }
+
+    return true;
 }
 
 bool ValidateMapBufferBase(Context *context, BufferBinding target)
diff --git a/src/tests/gl_tests/SemaphoreTest.cpp b/src/tests/gl_tests/SemaphoreTest.cpp
index 564e391..0ad11d0 100644
--- a/src/tests/gl_tests/SemaphoreTest.cpp
+++ b/src/tests/gl_tests/SemaphoreTest.cpp
@@ -48,6 +48,21 @@
     EXPECT_GL_NO_ERROR();
 }
 
+// glImportSemaphoreFdEXT must fail for handle types that are not file descriptors.
+TEST_P(SemaphoreTest, ShouldFailValidationOnImportFdUnsupportedHandleType)
+{
+    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));
+
+    {
+        GLSemaphore semaphore;
+        int fd = -1;
+        glImportSemaphoreFdEXT(semaphore, GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, fd);
+        EXPECT_GL_ERROR(GL_INVALID_ENUM);
+    }
+
+    EXPECT_GL_NO_ERROR();
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these
 // tests should be run against.
 ANGLE_INSTANTIATE_TEST(SemaphoreTest,
diff --git a/src/tests/gl_tests/VulkanExternalImageTest.cpp b/src/tests/gl_tests/VulkanExternalImageTest.cpp
index 1f59880..96e093b 100644
--- a/src/tests/gl_tests/VulkanExternalImageTest.cpp
+++ b/src/tests/gl_tests/VulkanExternalImageTest.cpp
@@ -55,7 +55,7 @@
 };
 
 // glImportMemoryFdEXT must be able to import a valid opaque fd.
-TEST_P(VulkanExternalImageTest, ShouldImportOpaqueFd)
+TEST_P(VulkanExternalImageTest, ShouldImportMemoryOpaqueFd)
 {
     ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
 
@@ -90,6 +90,35 @@
     vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
 }
 
+// glImportSemaphoreFdEXT must be able to import a valid opaque fd.
+TEST_P(VulkanExternalImageTest, ShouldImportSemaphoreOpaqueFd)
+{
+    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));
+
+    VulkanExternalHelper helper;
+    helper.initialize();
+
+    ANGLE_SKIP_TEST_IF(!helper.canCreateSemaphoreOpaqueFd());
+
+    VkSemaphore vkSemaphore = VK_NULL_HANDLE;
+    VkResult result         = helper.createSemaphoreOpaqueFd(&vkSemaphore);
+    EXPECT_EQ(result, VK_SUCCESS);
+
+    int fd = kInvalidFd;
+    result = helper.exportSemaphoreOpaqueFd(vkSemaphore, &fd);
+    EXPECT_EQ(result, VK_SUCCESS);
+    EXPECT_NE(fd, kInvalidFd);
+
+    {
+        GLSemaphore glSemaphore;
+        glImportSemaphoreFdEXT(glSemaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd);
+    }
+
+    EXPECT_GL_NO_ERROR();
+
+    vkDestroySemaphore(helper.getDevice(), vkSemaphore, nullptr);
+}
+
 // Test creating and clearing a simple RGBA8 texture in a opaque fd.
 TEST_P(VulkanExternalImageTest, ShouldClearOpaqueFdRGBA8)
 {
diff --git a/src/tests/test_utils/VulkanExternalHelper.cpp b/src/tests/test_utils/VulkanExternalHelper.cpp
index f43b7cd..727ea5c 100644
--- a/src/tests/test_utils/VulkanExternalHelper.cpp
+++ b/src/tests/test_utils/VulkanExternalHelper.cpp
@@ -277,6 +277,11 @@
             vkGetInstanceProcAddr(mInstance, "vkGetPhysicalDeviceImageFormatProperties2"));
     vkGetMemoryFdKHR = reinterpret_cast<PFN_vkGetMemoryFdKHR>(
         vkGetInstanceProcAddr(mInstance, "vkGetMemoryFdKHR"));
+    vkGetSemaphoreFdKHR = reinterpret_cast<PFN_vkGetSemaphoreFdKHR>(
+        vkGetInstanceProcAddr(mInstance, "vkGetSemaphoreFdKHR"));
+    vkGetPhysicalDeviceExternalSemaphorePropertiesKHR =
+        reinterpret_cast<PFN_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR>(
+            vkGetInstanceProcAddr(mInstance, "vkGetPhysicalDeviceExternalSemaphorePropertiesKHR"));
 }
 
 bool VulkanExternalHelper::canCreateImageOpaqueFd(VkFormat format,
@@ -331,8 +336,8 @@
     {
         return false;
     }
-    if (!(externalImageFormatProperties.externalMemoryProperties.externalMemoryFeatures &
-          kRequiredFeatures))
+    if ((externalImageFormatProperties.externalMemoryProperties.externalMemoryFeatures &
+         kRequiredFeatures) != kRequiredFeatures)
     {
         return false;
     }
@@ -434,4 +439,62 @@
     return vkGetMemoryFdKHR(mDevice, &memoryGetFdInfo, fd);
 }
 
+bool VulkanExternalHelper::canCreateSemaphoreOpaqueFd() const
+{
+    if (!mHasExternalSemaphoreFd || !vkGetPhysicalDeviceExternalSemaphorePropertiesKHR)
+    {
+        return false;
+    }
+
+    VkPhysicalDeviceExternalSemaphoreInfo externalSemaphoreInfo = {
+        /* .sType = */ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+        /* .pNext = */ nullptr,
+        /* .handleType = */ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT,
+    };
+
+    VkExternalSemaphoreProperties externalSemaphoreProperties = {};
+    vkGetPhysicalDeviceExternalSemaphorePropertiesKHR(mPhysicalDevice, &externalSemaphoreInfo,
+                                                      &externalSemaphoreProperties);
+
+    constexpr VkExternalSemaphoreFeatureFlags kRequiredFeatures =
+        VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT;
+
+    if ((externalSemaphoreProperties.externalSemaphoreFeatures & kRequiredFeatures) !=
+        kRequiredFeatures)
+    {
+        return false;
+    }
+
+    return true;
+}
+
+VkResult VulkanExternalHelper::createSemaphoreOpaqueFd(VkSemaphore *semaphore)
+{
+    VkExportSemaphoreCreateInfo exportSemaphoreCreateInfo = {
+        /* .sType = */ VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
+        /* .pNext = */ nullptr,
+        /* .handleTypes = */ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT,
+    };
+
+    VkSemaphoreCreateInfo semaphoreCreateInfo = {
+        /* .sType = */ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+        /* .pNext = */ &exportSemaphoreCreateInfo,
+        /* .flags = */ 0,
+    };
+
+    return vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, semaphore);
+}
+
+VkResult VulkanExternalHelper::exportSemaphoreOpaqueFd(VkSemaphore semaphore, int *fd)
+{
+    VkSemaphoreGetFdInfoKHR semaphoreGetFdInfo = {
+        /* .sType = */ VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR,
+        /* .pNext = */ nullptr,
+        /* .semaphore = */ semaphore,
+        /* .handleType = */ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT,
+    };
+
+    return vkGetSemaphoreFdKHR(mDevice, &semaphoreGetFdInfo, fd);
+}
+
 }  // namespace angle
diff --git a/src/tests/test_utils/VulkanExternalHelper.h b/src/tests/test_utils/VulkanExternalHelper.h
index 519a9f0..e1939e8 100644
--- a/src/tests/test_utils/VulkanExternalHelper.h
+++ b/src/tests/test_utils/VulkanExternalHelper.h
@@ -35,6 +35,11 @@
                                    VkDeviceSize *deviceMemorySizeOut);
     VkResult exportMemoryOpaqueFd(VkDeviceMemory deviceMemory, int *fd);
 
+    // VK_KHR_external_semaphore_fd
+    bool canCreateSemaphoreOpaqueFd() const;
+    VkResult createSemaphoreOpaqueFd(VkSemaphore *semaphore);
+    VkResult exportSemaphoreOpaqueFd(VkSemaphore semaphore, int *fd);
+
   private:
     VkInstance mInstance             = VK_NULL_HANDLE;
     VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
@@ -50,6 +55,9 @@
     PFN_vkGetPhysicalDeviceImageFormatProperties2 vkGetPhysicalDeviceImageFormatProperties2 =
         nullptr;
     PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR = nullptr;
+    PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr;
+    PFN_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR
+        vkGetPhysicalDeviceExternalSemaphorePropertiesKHR = nullptr;
 };
 
 }  // namespace angle
diff --git a/src/tests/test_utils/gl_raii.h b/src/tests/test_utils/gl_raii.h
index fd9ed02..4a2c9c1 100644
--- a/src/tests/test_utils/gl_raii.h
+++ b/src/tests/test_utils/gl_raii.h
@@ -101,6 +101,11 @@
   public:
     GLSampler() : GLWrapper(&glGenSamplers, &glDeleteSamplers) {}
 };
+class GLSemaphore : public GLWrapper
+{
+  public:
+    GLSemaphore() : GLWrapper(&glGenSemaphoresEXT, &glDeleteSemaphoresEXT) {}
+};
 class GLTransformFeedback : public GLWrapper
 {
   public: