[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/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