[vulkan] Implement VK_FUCHSIA_external_memory extension.

This implements external memory using a Zircon VMO handle
that can be transferred between Fuchsia processes easily.

Bug: b/140419396
Change-Id: I81cecec35b218eb22f3318ddec7b790b89e5ce09
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/36168
Tested-by: David Turner <digit@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/Vulkan/VkDeviceMemory.cpp b/src/Vulkan/VkDeviceMemory.cpp
index 6f3ad1c..014557f 100644
--- a/src/Vulkan/VkDeviceMemory.cpp
+++ b/src/Vulkan/VkDeviceMemory.cpp
@@ -51,6 +51,13 @@
 	}
 #endif
 
+#if VK_USE_PLATFORM_FUCHSIA
+	virtual VkResult exportHandle(zx_handle_t *pHandle) const
+	{
+		return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+	}
+#endif
+
 protected:
 	ExternalBase() = default;
 };
@@ -219,6 +226,10 @@
 #	endif
 #endif
 
+#if VK_USE_PLATFORM_FUCHSIA
+#	include "VkDeviceMemoryExternalFuchsia.hpp"
+#endif
+
 namespace vk {
 
 static void findTraits(const VkMemoryAllocateInfo *pAllocateInfo,
@@ -236,6 +247,12 @@
 		return;
 	}
 #endif
+#if VK_USE_PLATFORM_FUCHSIA
+	if(parseCreateInfo<zircon::VmoExternalMemory>(pAllocateInfo, pTraits))
+	{
+		return;
+	}
+#endif
 	if(parseCreateInfo<ExternalMemoryHost>(pAllocateInfo, pTraits))
 	{
 		return;
@@ -348,4 +365,11 @@
 }
 #endif
 
+#if VK_USE_PLATFORM_FUCHSIA
+VkResult DeviceMemory::exportHandle(zx_handle_t *pHandle) const
+{
+	return external->exportHandle(pHandle);
+}
+#endif
+
 }  // namespace vk
diff --git a/src/Vulkan/VkDeviceMemory.hpp b/src/Vulkan/VkDeviceMemory.hpp
index e99fa89..ea014d6 100644
--- a/src/Vulkan/VkDeviceMemory.hpp
+++ b/src/Vulkan/VkDeviceMemory.hpp
@@ -36,6 +36,10 @@
 	static VkResult getAhbProperties(const struct AHardwareBuffer *buffer, VkAndroidHardwareBufferPropertiesANDROID *pProperties);
 #endif
 
+#if VK_USE_PLATFORM_FUCHSIA
+	VkResult exportHandle(zx_handle_t *pHandle) const;
+#endif
+
 	void destroy(const VkAllocationCallbacks *pAllocator);
 	VkResult allocate();
 	VkResult map(VkDeviceSize offset, VkDeviceSize size, void **ppData);
diff --git a/src/Vulkan/VkDeviceMemoryExternalFuchsia.hpp b/src/Vulkan/VkDeviceMemoryExternalFuchsia.hpp
new file mode 100644
index 0000000..0352046
--- /dev/null
+++ b/src/Vulkan/VkDeviceMemoryExternalFuchsia.hpp
@@ -0,0 +1,178 @@
+// 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.
+
+#include "VkStringify.hpp"
+
+#include "System/Debug.hpp"
+
+#include <zircon/process.h>
+#include <zircon/syscalls.h>
+
+namespace zircon {
+
+class VmoExternalMemory : public vk::DeviceMemory::ExternalBase
+{
+public:
+	// Helper struct to parse the VkMemoryAllocateInfo.pNext chain and
+	// extract relevant information related to the handle type supported
+	// by this DeviceMemory::ExternalBase subclass.
+	struct AllocateInfo
+	{
+		bool importHandle = false;
+		bool exportHandle = false;
+		zx_handle_t handle = ZX_HANDLE_INVALID;
+
+		AllocateInfo() = default;
+
+		// Parse the VkMemoryAllocateInfo->pNext chain to initialize a AllocateInfo.
+		AllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
+		{
+			const auto *extInfo = reinterpret_cast<const VkBaseInStructure *>(pAllocateInfo->pNext);
+			while(extInfo)
+			{
+				switch(extInfo->sType)
+				{
+					case VK_STRUCTURE_TYPE_TEMP_IMPORT_MEMORY_ZIRCON_HANDLE_INFO_FUCHSIA:
+					{
+						const auto *importInfo = reinterpret_cast<const VkImportMemoryZirconHandleInfoFUCHSIA *>(extInfo);
+
+						if(importInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA)
+						{
+							UNSUPPORTED("importInfo->handleType");
+						}
+						importHandle = true;
+						handle = importInfo->handle;
+						break;
+					}
+					case VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO:
+					{
+						const auto *exportInfo = reinterpret_cast<const VkExportMemoryAllocateInfo *>(extInfo);
+
+						if(exportInfo->handleTypes != VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA)
+						{
+							UNSUPPORTED("exportInfo->handleTypes");
+						}
+						exportHandle = true;
+						break;
+					}
+
+					default:
+						WARN("VkMemoryAllocateInfo->pNext sType = %s", vk::Stringify(extInfo->sType).c_str());
+				}
+				extInfo = extInfo->pNext;
+			}
+		}
+	};
+
+	static const VkExternalMemoryHandleTypeFlagBits typeFlagBit = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA;
+
+	static bool supportsAllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
+	{
+		AllocateInfo info(pAllocateInfo);
+		return info.importHandle || info.exportHandle;
+	}
+
+	explicit VmoExternalMemory(const VkMemoryAllocateInfo *pAllocateInfo)
+	    : allocateInfo(pAllocateInfo)
+	{
+	}
+
+	~VmoExternalMemory()
+	{
+		closeVmo();
+	}
+
+	VkResult allocate(size_t size, void **pBuffer) override
+	{
+		if(allocateInfo.importHandle)
+		{
+			// NOTE: handle ownership is passed to the VkDeviceMemory.
+			vmoHandle = allocateInfo.handle;
+		}
+		else
+		{
+			ASSERT(allocateInfo.exportHandle);
+			zx_status_t status = zx_vmo_create(size, 0, &vmoHandle);
+			if(status != ZX_OK)
+			{
+				TRACE("zx_vmo_create() returned %d", status);
+				return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+			}
+		}
+
+		// Now map it directly.
+		zx_vaddr_t addr = 0;
+		zx_status_t status = zx_vmar_map(zx_vmar_root_self(),
+		                                 ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
+		                                 0,  // vmar_offset
+		                                 vmoHandle,
+		                                 0,  // vmo_offset
+		                                 size,
+		                                 &addr);
+		if(status != ZX_OK)
+		{
+			TRACE("zx_vmar_map() failed with %d", status);
+			return VK_ERROR_MEMORY_MAP_FAILED;
+		}
+		*pBuffer = reinterpret_cast<void *>(addr);
+		return VK_SUCCESS;
+	}
+
+	void deallocate(void *buffer, size_t size) override
+	{
+		zx_status_t status = zx_vmar_unmap(zx_vmar_root_self(),
+		                                   reinterpret_cast<zx_vaddr_t>(buffer),
+		                                   size);
+		if(status != ZX_OK)
+		{
+			TRACE("zx_vmar_unmap() failed with %d", status);
+		}
+		closeVmo();
+	}
+
+	VkExternalMemoryHandleTypeFlagBits getFlagBit() const override
+	{
+		return typeFlagBit;
+	}
+
+	VkResult exportHandle(zx_handle_t *pHandle) const override
+	{
+		if(vmoHandle == ZX_HANDLE_INVALID)
+		{
+			return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+		}
+		zx_status_t status = zx_handle_duplicate(vmoHandle, ZX_RIGHT_SAME_RIGHTS, pHandle);
+		if(status != ZX_OK)
+		{
+			TRACE("zx_handle_duplicate() returned %d", status);
+			return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+		}
+		return VK_SUCCESS;
+	}
+
+private:
+	void closeVmo()
+	{
+		if(vmoHandle != ZX_HANDLE_INVALID)
+		{
+			zx_handle_close(vmoHandle);
+			vmoHandle = ZX_HANDLE_INVALID;
+		}
+	}
+
+	zx_handle_t vmoHandle = ZX_HANDLE_INVALID;
+	AllocateInfo allocateInfo;
+};
+
+}  // namespace zircon
diff --git a/src/Vulkan/VkGetProcAddress.cpp b/src/Vulkan/VkGetProcAddress.cpp
index 64ac9f8..95310d3 100644
--- a/src/Vulkan/VkGetProcAddress.cpp
+++ b/src/Vulkan/VkGetProcAddress.cpp
@@ -397,6 +397,16 @@
 	        MAKE_VULKAN_DEVICE_ENTRY(vkGetMemoryAndroidHardwareBufferANDROID),
 	    } },
 #endif
+
+#if VK_USE_PLATFORM_FUCHSIA
+	// VK_FUCHSIA_external_memory
+	{
+	    VK_FUCHSIA_EXTERNAL_MEMORY_EXTENSION_NAME,
+	    {
+	        MAKE_VULKAN_DEVICE_ENTRY(vkGetMemoryZirconHandleFUCHSIA),
+	        MAKE_VULKAN_DEVICE_ENTRY(vkGetMemoryZirconHandlePropertiesFUCHSIA),
+	    } },
+#endif
 };
 
 #undef MAKE_VULKAN_DEVICE_ENTRY
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index 331187a..fcb09da 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -43,6 +43,15 @@
 		return;
 	}
 #endif
+#if VK_USE_PLATFORM_FUCHSIA
+	if(handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA)
+	{
+		properties->compatibleHandleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA;
+		properties->exportFromImportedHandleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA;
+		properties->externalMemoryFeatures = VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT | VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT;
+		return;
+	}
+#endif
 	properties->compatibleHandleTypes = 0;
 	properties->exportFromImportedHandleTypes = 0;
 	properties->externalMemoryFeatures = 0;
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 3e3e648..07d2889 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -348,6 +348,7 @@
 
 #if VK_USE_PLATFORM_FUCHSIA
 	{ VK_FUCHSIA_EXTERNAL_SEMAPHORE_EXTENSION_NAME, VK_FUCHSIA_EXTERNAL_SEMAPHORE_SPEC_VERSION },
+	{ VK_FUCHSIA_EXTERNAL_MEMORY_EXTENSION_NAME, VK_FUCHSIA_EXTERNAL_MEMORY_SPEC_VERSION },
 #endif
 	{ VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, VK_EXT_PROVOKING_VERTEX_SPEC_VERSION },
 };
@@ -946,6 +947,10 @@
 					case VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID:
 						break;
 #endif
+#if VK_USE_PLATFORM_FUCHSIA
+					case VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA:
+						break;
+#endif
 					default:
 						UNSUPPORTED("exportInfo->handleTypes %u", exportInfo->handleTypes);
 						return VK_ERROR_INVALID_EXTERNAL_HANDLE;
@@ -967,6 +972,18 @@
 				}
 				break;
 			}
+#if VK_USE_PLATFORM_FUCHSIA
+			case VK_STRUCTURE_TYPE_TEMP_IMPORT_MEMORY_ZIRCON_HANDLE_INFO_FUCHSIA:
+			{
+				auto *importInfo = reinterpret_cast<const VkImportMemoryZirconHandleInfoFUCHSIA *>(allocationInfo);
+				if(importInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA)
+				{
+					UNSUPPORTED("importInfo->handleType %u", importInfo->handleType);
+					return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+				}
+				break;
+			}
+#endif  // VK_USE_PLATFORM_FUCHSIA
 			default:
 				LOG_TRAP("pAllocateInfo->pNext sType = %s", vk::Stringify(allocationInfo->sType).c_str());
 				break;
@@ -1039,6 +1056,45 @@
 	return VK_SUCCESS;
 }
 #endif  // SWIFTSHADER_EXTERNAL_MEMORY_OPAQUE_FD
+#if VK_USE_PLATFORM_FUCHSIA
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryZirconHandleFUCHSIA(VkDevice device, const VkMemoryGetZirconHandleInfoFUCHSIA *pGetHandleInfo, zx_handle_t *pHandle)
+{
+	TRACE("(VkDevice device = %p, const VkMemoryGetZirconHandleInfoFUCHSIA* pGetHandleInfo = %p, zx_handle_t* pHandle = %p",
+	      device, pGetHandleInfo, pHandle);
+
+	if(pGetHandleInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA)
+	{
+		UNSUPPORTED("pGetHandleInfo->handleType %u", pGetHandleInfo->handleType);
+		return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+	}
+	return vk::Cast(pGetHandleInfo->memory)->exportHandle(pHandle);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryZirconHandlePropertiesFUCHSIA(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, zx_handle_t handle, VkMemoryZirconHandlePropertiesFUCHSIA *pMemoryZirconHandleProperties)
+{
+	TRACE("(VkDevice device = %p, VkExternalMemoryHandleTypeFlagBits handleType = %x, zx_handle_t handle = %d, VkMemoryZirconHandlePropertiesFUCHSIA* pMemoryZirconHandleProperties = %p)",
+	      device, handleType, handle, pMemoryZirconHandleProperties);
+
+	if(handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA)
+	{
+		UNSUPPORTED("handleType %u", handleType);
+		return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+	}
+
+	if(handle == ZX_HANDLE_INVALID)
+	{
+		return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+	}
+
+	const VkPhysicalDeviceMemoryProperties &memoryProperties =
+	    vk::Cast(device)->getPhysicalDevice()->getMemoryProperties();
+
+	// All SwiftShader memory types support this!
+	pMemoryZirconHandleProperties->memoryTypeBits = (1U << memoryProperties.memoryTypeCount) - 1U;
+
+	return VK_SUCCESS;
+}
+#endif  // VK_USE_PLATFORM_FUCHSIA
 
 VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryHostPointerPropertiesEXT(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, const void *pHostPointer, VkMemoryHostPointerPropertiesEXT *pMemoryHostPointerProperties)
 {