[vulkan] Implement Linux-based external semaphore

Add external semaphore support for Linux and Android
based on a process-shared pthread mutex + condition
variable stored in a shared memory region backed by
memfd_create().

This takes care of waiting for external semaphores
in a background thread when invoked from a Yarn fiber.

Test: dEQP-VK.api.external.semaphore.opaque_fd*

Bug: b/140421726
Change-Id: Ifa74c807d3e33914e5a37dd96650c312246c8e4f
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/35939
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Chris Forbes <chrisforbes@google.com>
Tested-by: David Turner <digit@google.com>
Kokoro-Presubmit: David Turner <digit@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Vulkan/BUILD.gn b/src/Vulkan/BUILD.gn
index 8e19658..308b718 100644
--- a/src/Vulkan/BUILD.gn
+++ b/src/Vulkan/BUILD.gn
@@ -83,6 +83,15 @@
     "VkShaderModule.hpp",
     "VulkanPlatform.h",
   ]
+  if (is_linux || is_android) {
+    sources += [
+      "VkSemaphoreExternalLinux.hpp",
+    ]
+  } else {
+    sources += [
+      "VkSemaphoreExternalNone.hpp",
+    ]
+  }
 }
 
 swiftshader_shared_library("swiftshader_libvulkan") {
diff --git a/src/Vulkan/VkConfig.h b/src/Vulkan/VkConfig.h
index 3bc9819..9badfd8 100644
--- a/src/Vulkan/VkConfig.h
+++ b/src/Vulkan/VkConfig.h
@@ -84,4 +84,8 @@
 
 }
 
+#if VK_USE_PLATFORM_XLIB_KHR || VK_USE_PLATFORM_ANDROID_KHR
+#define SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD     1
+#endif
+
 #endif // VK_CONFIG_HPP_
diff --git a/src/Vulkan/VkGetProcAddress.cpp b/src/Vulkan/VkGetProcAddress.cpp
index 9e5d8f4..28bdcaa 100644
--- a/src/Vulkan/VkGetProcAddress.cpp
+++ b/src/Vulkan/VkGetProcAddress.cpp
@@ -334,6 +334,17 @@
 		}
 	},
 #endif
+
+#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+	// VK_KHR_external_semaphore_fd
+	{
+		VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
+		{
+			MAKE_VULKAN_DEVICE_ENTRY(vkGetSemaphoreFdKHR),
+			MAKE_VULKAN_DEVICE_ENTRY(vkImportSemaphoreFdKHR),
+		}
+	},
+#endif
 };
 
 #undef MAKE_VULKAN_DEVICE_ENTRY
@@ -443,4 +454,4 @@
 	}
 };
 
-#endif
\ No newline at end of file
+#endif
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index 44a2ba4..5ccba6f 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -357,6 +357,15 @@
 
 void PhysicalDevice::getProperties(const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties) const
 {
+#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+	if (pExternalSemaphoreInfo->handleType == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT)
+	{
+		pExternalSemaphoreProperties->compatibleHandleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
+		pExternalSemaphoreProperties->exportFromImportedHandleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
+		pExternalSemaphoreProperties->externalSemaphoreFeatures = VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT;
+		return;
+	}
+#endif
 	pExternalSemaphoreProperties->compatibleHandleTypes = 0;
 	pExternalSemaphoreProperties->exportFromImportedHandleTypes = 0;
 	pExternalSemaphoreProperties->externalSemaphoreFeatures = 0;
diff --git a/src/Vulkan/VkSemaphore.cpp b/src/Vulkan/VkSemaphore.cpp
index 04a1c26..62ff418 100644
--- a/src/Vulkan/VkSemaphore.cpp
+++ b/src/Vulkan/VkSemaphore.cpp
@@ -14,8 +14,21 @@
 
 #include "VkSemaphore.hpp"
 
+#include "VkConfig.h"
+
+#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+#include "VkSemaphoreExternalLinux.hpp"
+#else
+#include "VkSemaphoreExternalNone.hpp"
+#endif
+
+#include "marl/blockingcall.h"
 #include "marl/conditionvariable.h"
+
+#include <functional>
+#include <memory>
 #include <mutex>
+#include <utility>
 
 namespace vk
 {
@@ -24,17 +37,115 @@
 class Semaphore::Impl
 {
 public:
-	Impl() = default;
+	// Create a new instance. The external instance will be allocated only
+	// the pCreateInfo->pNext chain indicates it needs to be exported.
+	Impl(const VkSemaphoreCreateInfo* pCreateInfo) {
+		bool exportSemaphore = false;
+		for (const auto* nextInfo = reinterpret_cast<const VkBaseInStructure*>(pCreateInfo->pNext);
+			 nextInfo != nullptr; nextInfo = nextInfo->pNext)
+		{
+			if (nextInfo->sType == VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO)
+			{
+				const auto* exportInfo = reinterpret_cast<const VkExportSemaphoreCreateInfo *>(nextInfo);
+				if (exportInfo->handleTypes != External::kExternalSemaphoreHandleType)
+				{
+					UNIMPLEMENTED("exportInfo->handleTypes");
+				}
+				exportSemaphore = true;
+				break;
+			}
+		}
+
+		if (exportSemaphore)
+		{
+			allocateExternalNoInit();
+			external->init();
+		}
+	}
+
+	~Impl() {
+		deallocateExternal();
+	}
+
+	// Deallocate the External semaphore if any.
+	void deallocateExternal()
+	{
+		if (external)
+		{
+			external->~External();
+			external = nullptr;
+		}
+	}
+
+	// Allocate the external semaphore.
+	// Note that this does not allocate the internal resource, which must be
+	// performed by calling external->init(), or importing one using
+	// a platform-specific external->importXXX(...) method.
+	void allocateExternalNoInit()
+	{
+		external = new (externalStorage) External();
+	}
 
 	void wait()
 	{
+		if (external)
+		{
+			if (!external->tryWait())
+			{
+				// Dispatch the external wait to a background thread.
+				// Even if this creates a new thread on each
+				// call, it is assumed that this is negligible
+				// compared with the actual semaphore wait()
+				// operation.
+				marl::blocking_call([this](){
+					external->wait();
+				});
+			}
+
+			// If the import was temporary, reset the semaphore to its
+			// permanent state by getting rid of |external|.
+			// See "6.4.5. Importing Semaphore Payloads" in Vulkan 1.1 spec.
+			if (temporaryImport)
+			{
+				deallocateExternal();
+				temporaryImport = false;
+			}
+		}
+		else
+		{
+			waitInternal();
+		}
+	}
+
+	void signal()
+	{
+		if (external)
+		{
+			// Assumes that signalling an external semaphore is non-blocking,
+			// so it can be performed directly either from a fiber or thread.
+			external->signal();
+		}
+		else
+		{
+			signalInternal();
+		}
+	}
+
+private:
+	// Necessary to make ::importXXX() and ::exportXXX() simpler.
+	friend Semaphore;
+
+	void waitInternal()
+	{
+		// Wait on the marl condition variable only.
 		std::unique_lock<std::mutex> lock(mutex);
 		condition.wait(lock, [this]{ return this->signaled; });
 		signaled = false;  // Vulkan requires resetting after waiting.
 	}
 
-	void signal()
+	void signalInternal()
 	{
+		// Signal the marl condition variable only.
 		std::unique_lock<std::mutex> lock(mutex);
 		if (!signaled)
 		{
@@ -43,15 +154,23 @@
 		}
 	}
 
-private:
+	// Implementation of a non-external semaphore based on Marl.
 	std::mutex mutex;
 	marl::ConditionVariable condition;
 	bool signaled = false;
+
+	// Optional external semaphore data might be referenced and stored here.
+	External* external = nullptr;
+
+	// Set to true if |external| comes from a temporary import.
+	bool temporaryImport = false;
+
+	alignas(External) char externalStorage[sizeof(External)];
 };
 
 Semaphore::Semaphore(const VkSemaphoreCreateInfo* pCreateInfo, void* mem)
 {
-	impl = new (mem) Impl();
+	impl = new (mem) Impl(pCreateInfo);
 }
 
 void Semaphore::destroy(const VkAllocationCallbacks* pAllocator)
@@ -75,4 +194,36 @@
 	impl->signal();
 }
 
+#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+VkResult Semaphore::importFd(int fd, bool temporaryImport)
+{
+	std::unique_lock<std::mutex> lock(impl->mutex);
+	if (!impl->external)
+	{
+		impl->allocateExternalNoInit();
+	}
+	VkResult result = impl->external->importFd(fd);
+	if (result != VK_SUCCESS)
+	{
+		impl->deallocateExternal();
+	}
+	else
+	{
+		impl->temporaryImport = temporaryImport;
+	}
+	return result;
+}
+
+VkResult Semaphore::exportFd(int* pFd) const
+{
+	std::unique_lock<std::mutex> lock(impl->mutex);
+	if (!impl->external)
+	{
+		TRACE("Cannot export non-external semaphore");
+		return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+	}
+	return impl->external->exportFd(pFd);
+}
+#endif  // SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+
 }  // namespace vk
diff --git a/src/Vulkan/VkSemaphore.hpp b/src/Vulkan/VkSemaphore.hpp
index 1f9b160..d691c41 100644
--- a/src/Vulkan/VkSemaphore.hpp
+++ b/src/Vulkan/VkSemaphore.hpp
@@ -15,6 +15,7 @@
 #ifndef VK_SEMAPHORE_HPP_
 #define VK_SEMAPHORE_HPP_
 
+#include "VkConfig.h"
 #include "VkObject.hpp"
 
 namespace vk
@@ -38,7 +39,13 @@
 
 	void signal();
 
+#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+	VkResult importFd(int fd, bool temporaryImport);
+	VkResult exportFd(int* pFd) const;
+#endif
+
 private:
+	class External;
 	class Impl;
 	Impl* impl = nullptr;
 };
diff --git a/src/Vulkan/VkSemaphoreExternalLinux.hpp b/src/Vulkan/VkSemaphoreExternalLinux.hpp
new file mode 100644
index 0000000..05983cf
--- /dev/null
+++ b/src/Vulkan/VkSemaphoreExternalLinux.hpp
@@ -0,0 +1,246 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef VK_SEMAPHORE_EXTERNAL_LINUX_H_
+#define VK_SEMAPHORE_EXTERNAL_LINUX_H_
+
+#include "System/Linux/MemFd.hpp"
+#include "System/Memory.hpp"
+
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+#include <sys/mman.h>
+
+// An external semaphore implementation for Linux, that uses memfd-backed
+// shared memory regions as the underlying implementation. The region contains
+// a single SharedSemaphore instance, which is a reference-counted semaphore
+// implementation based on a pthread process-shared mutex + condition variable
+// pair.
+//
+// This implementation works on any Linux system with at least kernel 3.17
+// (which should be sufficient for any not-so-recent Android system) and doesn't
+// require special libraries installed on the system.
+//
+// NOTE: This is not interoperable with other Linux ICDs that use Linux kernel
+// sync file objects (which correspond to
+// VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) instead.
+//
+
+namespace linux
+{
+
+// A process-shared semaphore implementation that can be stored in
+// a process-shared memory region. It also includes a reference count to
+// ensure it is only destroyed when the last reference to it is dropped.
+class SharedSemaphore
+{
+public:
+	SharedSemaphore()
+	{
+		pthread_mutexattr_t mattr;
+		pthread_mutexattr_init(&mattr);
+		pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
+		pthread_mutex_init(&mutex, &mattr);
+		pthread_mutexattr_destroy(&mattr);
+
+		pthread_condattr_t cattr;
+		pthread_condattr_init(&cattr);
+		pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
+		pthread_cond_init(&cond, &cattr);
+		pthread_condattr_destroy(&cattr);
+	}
+
+	~SharedSemaphore()
+	{
+		pthread_cond_destroy(&cond);
+		pthread_mutex_destroy(&mutex);
+	}
+
+	// Increment reference count.
+	void addRef()
+	{
+		pthread_mutex_lock(&mutex);
+		ref_count++;
+		pthread_mutex_unlock(&mutex);
+	}
+
+	// Decrement reference count and returns true iff it reaches 0.
+	bool deref()
+	{
+		pthread_mutex_lock(&mutex);
+		bool result = (--ref_count == 0);
+		pthread_mutex_unlock(&mutex);
+		return result;
+	}
+
+	void wait()
+	{
+		pthread_mutex_lock(&mutex);
+		while (!signaled)
+		{
+			pthread_cond_wait(&cond, &mutex);
+		}
+		// From Vulkan 1.1.119 spec, section 6.4.2:
+		// Unlike fences or events, the act of waiting for a semaphore also
+		// unsignals that semaphore.
+		signaled = false;
+		pthread_mutex_unlock(&mutex);
+	}
+
+	// Just like wait() but never blocks. Returns true if the semaphore
+	// was signaled (and reset by the function), or false otherwise.
+	// Used to avoid using a background thread for waiting in the case
+	// where the semaphore is already signaled.
+	bool tryWait()
+	{
+		pthread_mutex_lock(&mutex);
+		bool result = signaled;
+		if (result)
+		{
+			signaled = false;
+		}
+		pthread_mutex_unlock(&mutex);
+		return result;
+	}
+
+	void signal()
+	{
+		pthread_mutex_lock(&mutex);
+		signaled = true;
+		pthread_cond_broadcast(&cond);
+		pthread_mutex_unlock(&mutex);
+	}
+
+private:
+	pthread_mutex_t mutex;
+	pthread_cond_t cond;
+	int ref_count = 1;
+	bool signaled = false;
+};
+
+}  // namespace linux
+
+namespace vk
+{
+
+class Semaphore::External {
+public:
+	// The type of external semaphore handle types supported by this implementation.
+	static const VkExternalSemaphoreHandleTypeFlags kExternalSemaphoreHandleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
+
+	// Default constructor. Note that one should call either init() or
+	// importFd() before any call to wait() or signal().
+	External() = default;
+
+	~External() { close(); }
+
+	// Initialize instance by creating a new shared memory region.
+	void init()
+	{
+		// Allocate or import the region's file descriptor.
+		const size_t size = sw::memoryPageSize();
+		// To be exportable, the PosixSemaphore must be stored in a shared
+		// memory region.
+		static int counter = 0;
+		char name[40];
+		snprintf(name, sizeof(name), "SwiftShader.Semaphore.%d", ++counter);
+		if (!memfd.allocate(name, size))
+		{
+			ABORT("memfd.allocate() returned %s", strerror(errno));
+		}
+		mapRegion(size, true);
+	}
+
+	// Import an existing semaphore through its file descriptor.
+	VkResult importFd(int fd)
+	{
+		close();
+		memfd.importFd(fd);
+		mapRegion(sw::memoryPageSize(), false);
+		return VK_SUCCESS;
+	}
+
+	// Export the current semaphore as a duplicated file descriptor to the same
+	// region. This can be consumed by importFd() running in a different
+	// process.
+	VkResult exportFd(int* pFd) const
+	{
+		int fd = memfd.exportFd();
+		if (fd < 0)
+		{
+			return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+		}
+		*pFd = fd;
+		return VK_SUCCESS;
+	}
+
+	void wait()
+	{
+		semaphore->wait();
+	}
+
+	bool tryWait()
+	{
+		return semaphore->tryWait();
+	}
+
+	void signal()
+	{
+		semaphore->signal();
+	}
+
+private:
+	// Unmap the semaphore if needed and close its file descriptor.
+	void close()
+	{
+		if (semaphore)
+		{
+			if (semaphore->deref())
+			{
+				semaphore->~SharedSemaphore();
+			}
+			memfd.unmap(semaphore, sw::memoryPageSize());
+			memfd.close();
+			semaphore = nullptr;
+		}
+	}
+
+	// Remap the shared region and setup the semaphore or increment its reference count.
+	void mapRegion(size_t size, bool needInitialization)
+	{
+		// Map the region into memory and point the semaphore to it.
+		void* addr = memfd.mapReadWrite(0, size);
+		if (!addr)
+		{
+			ABORT("mmap() failed: %s", strerror(errno));
+		}
+		semaphore = reinterpret_cast<linux::SharedSemaphore *>(addr);
+		if (needInitialization)
+		{
+			new (semaphore) linux::SharedSemaphore();
+		}
+		else
+		{
+			semaphore->addRef();
+		}
+	}
+
+	linux::MemFd memfd;
+	linux::SharedSemaphore* semaphore = nullptr;
+};
+
+}  // namespace vk
+
+#endif  // VK_SEMAPHORE_EXTERNAL_LINUX_H_
diff --git a/src/Vulkan/VkSemaphoreExternalNone.hpp b/src/Vulkan/VkSemaphoreExternalNone.hpp
new file mode 100644
index 0000000..d056e6c
--- /dev/null
+++ b/src/Vulkan/VkSemaphoreExternalNone.hpp
@@ -0,0 +1,41 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef VK_SEMAPHORE_EXTERNAL_NONE_H_
+#define VK_SEMAPHORE_EXTERNAL_NONE_H_
+
+namespace vk
+{
+
+// Empty external sempahore implementation.
+class Semaphore::External {
+public:
+	// The type of external semaphore handle types supported by this implementation.
+	static const VkExternalSemaphoreHandleTypeFlags kExternalSemaphoreHandleType = 0;
+
+	void init() {}
+
+	void wait() {}
+
+	bool tryWait() { return true; }
+
+	void signal() {}
+
+private:
+	int dummy;
+};
+
+}  // namespace vk
+
+#endif  // VK_SEMAPHORE_EXTERNAL_NONE_H_
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 11bac00..320ec99 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -228,6 +228,9 @@
 	// (from KHR_swapchain v70) to vkBindImageMemory2.
 	{ VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME, 7 },
 #endif
+#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+	{ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_SPEC_VERSION },
+#endif
 };
 
 VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance)
@@ -945,9 +948,9 @@
 	TRACE("(VkDevice device = %p, const VkSemaphoreCreateInfo* pCreateInfo = %p, const VkAllocationCallbacks* pAllocator = %p, VkSemaphore* pSemaphore = %p)",
 	      device, pCreateInfo, pAllocator, pSemaphore);
 
-	if(pCreateInfo->pNext || pCreateInfo->flags)
+	if(pCreateInfo->flags)
 	{
-		UNIMPLEMENTED("pCreateInfo->pNext || pCreateInfo->flags");
+		UNIMPLEMENTED("pCreateInfo->flags");
 	}
 
 	return vk::Semaphore::Create(pAllocator, pCreateInfo, pSemaphore);
@@ -961,6 +964,35 @@
 	vk::destroy(semaphore, pAllocator);
 }
 
+#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreFdKHR(VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd)
+{
+	TRACE("(VkDevice device = %p, const VkSemaphoreGetFdInfoKHR* pGetFdInfo = %p, int* pFd = %p)",
+	      device, static_cast<const void*>(pGetFdInfo), static_cast<void*>(pFd));
+
+	if (pGetFdInfo->handleType != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT)
+	{
+		UNIMPLEMENTED("pGetFdInfo->handleType");
+	}
+
+	return vk::Cast(pGetFdInfo->semaphore)->exportFd(pFd);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreFdKHR(VkDevice device, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreInfo)
+{
+	TRACE("(VkDevice device = %p, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreInfo = %p",
+	      device, static_cast<const void*>(pImportSemaphoreInfo));
+
+	if (pImportSemaphoreInfo->handleType != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT)
+	{
+		UNIMPLEMENTED("pImportSemaphoreInfo->handleType");
+	}
+	bool temporaryImport = (pImportSemaphoreInfo->flags & VK_SEMAPHORE_IMPORT_TEMPORARY_BIT) != 0;
+
+	return vk::Cast(pImportSemaphoreInfo->semaphore)->importFd(pImportSemaphoreInfo->fd, temporaryImport);
+}
+#endif  // SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
+
 VKAPI_ATTR VkResult VKAPI_CALL vkCreateEvent(VkDevice device, const VkEventCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkEvent* pEvent)
 {
 	TRACE("(VkDevice device = %p, const VkEventCreateInfo* pCreateInfo = %p, const VkAllocationCallbacks* pAllocator = %p, VkEvent* pEvent = %p)",