Support version 7 of the ICD interface.

The Vulkan loader requires this version for direct driver loading, which
Chromium / Dawn would like to use for loading Swiftshader with the
Vulkan loader (so that VVLs can be inserted).

 - Version 4 adds vk_icdGetPhysicalDeviceProcAddr.
 - Version 5 adds a check for the apiVersion depending on the negotiated
   ICD interface version.
 - Version 6 adds vk_icdEnumerateAdapterPhysicalDevices on Windows
   (which is a noop for Swiftshader)
 - Version 7 doesn't require any code change.

Bug: dawn:2145

Change-Id: I82cce575662b4b8caf6ba3538f9a954a49eca0bf
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/72488
Tested-by: Corentin Wallez <cwallez@google.com>
Commit-Queue: Corentin Wallez <cwallez@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/Vulkan/VkGetProcAddress.cpp b/src/Vulkan/VkGetProcAddress.cpp
index d61f600..8a8b759 100644
--- a/src/Vulkan/VkGetProcAddress.cpp
+++ b/src/Vulkan/VkGetProcAddress.cpp
@@ -16,6 +16,8 @@
 #include "VkDevice.hpp"
 
 #include <string>
+#include <tuple>
+#include <type_traits>
 #include <unordered_map>
 #include <vector>
 
@@ -40,17 +42,51 @@
 	MAKE_VULKAN_GLOBAL_ENTRY(vkEnumerateInstanceExtensionProperties),
 	MAKE_VULKAN_GLOBAL_ENTRY(vkEnumerateInstanceLayerProperties),
 	MAKE_VULKAN_GLOBAL_ENTRY(vkEnumerateInstanceVersion),
+
+	MAKE_VULKAN_GLOBAL_ENTRY(vk_icdGetInstanceProcAddr),
+	MAKE_VULKAN_GLOBAL_ENTRY(vk_icdNegotiateLoaderICDInterfaceVersion),
+#if VK_USE_PLATFORM_WIN32_KHR
+	MAKE_VULKAN_GLOBAL_ENTRY(vk_icdEnumerateAdapterPhysicalDevices),
+#endif  // VK_USE_PLATFORM_WIN32_KHR
 };
 #undef MAKE_VULKAN_GLOBAL_ENTRY
 
 // Functions that can be obtained through GetInstanceProcAddr with an instance object
-#define MAKE_VULKAN_INSTANCE_ENTRY(aFunction)                         \
-	{                                                                 \
-#		aFunction, reinterpret_cast < PFN_vkVoidFunction>(aFunction) \
+struct InstanceFunctionEntry
+{
+	PFN_vkVoidFunction pfn;
+	// True if the first argument is a VkPhysicalDevice. See
+	// https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderDriverInterface.md#reason-for-adding-vk_icdgetphysicaldeviceprocaddr
+	bool isPhysicalDeviceFn;
+};
+
+// Template magic to detect if the first argument of a C function is a VkPhysicalDevice.
+template<typename T>
+struct FunctionArgs
+{};
+
+template<typename R, typename... Args>
+struct FunctionArgs<VKAPI_ATTR R VKAPI_CALL(Args...)>
+{
+	using Tuple = std::tuple<Args...>;
+	using FirstType = typename std::tuple_element<0, Tuple>::type;
+};
+
+template<typename T>
+constexpr bool HasPhysicalDeviceFirstArgument =
+    std::is_same<typename FunctionArgs<T>::FirstType, VkPhysicalDevice>::value;
+
+#define MAKE_VULKAN_INSTANCE_ENTRY(aFunction)                       \
+	{                                                               \
+		#aFunction,                                                 \
+		{                                                           \
+			reinterpret_cast<PFN_vkVoidFunction>(aFunction),        \
+			    HasPhysicalDeviceFirstArgument<decltype(aFunction)> \
+		}                                                           \
 	}
 
 // TODO(b/208256248): Avoid exit-time destructor.
-static const std::unordered_map<std::string, PFN_vkVoidFunction> instanceFunctionPointers = {
+static const std::unordered_map<std::string, InstanceFunctionEntry> instanceFunctionPointers = {
 
 	MAKE_VULKAN_INSTANCE_ENTRY(vkDestroyInstance),
 	MAKE_VULKAN_INSTANCE_ENTRY(vkEnumeratePhysicalDevices),
@@ -652,7 +688,7 @@
 		auto instanceFunction = instanceFunctionPointers.find(std::string(pName));
 		if(instanceFunction != instanceFunctionPointers.end())
 		{
-			return instanceFunction->second;
+			return instanceFunction->second.pfn;
 		}
 
 		auto deviceFunction = deviceFunctionPointers.find(std::string(pName));
@@ -674,6 +710,33 @@
 	return nullptr;
 }
 
+PFN_vkVoidFunction GetPhysicalDeviceProcAddr(Instance *instance, const char *pName)
+{
+	// This function must return nullptr if the name is not known, or the function doesn't take a
+	// VkPhysicalDevice as the first argument. All functions that have a VkPhysicalDevice as first
+	// argument are instance function, except for vkGetPhysicalDeviceToolPropertiesEXT which seems
+	// to have been miscategorized as a device extension when it was made. So we special case that
+	// funcion.
+	std::string name = pName;
+	if(name == "vkGetPhysicalDeviceToolPropertiesEXT")
+	{
+		return reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceToolPropertiesEXT);
+	}
+
+	auto instanceFunction = instanceFunctionPointers.find(name);
+	if(instanceFunction == instanceFunctionPointers.end())
+	{
+		return nullptr;
+	}
+
+	if(!instanceFunction->second.isPhysicalDeviceFn)
+	{
+		return nullptr;
+	}
+
+	return instanceFunction->second.pfn;
+}
+
 PFN_vkVoidFunction GetDeviceProcAddr(Device *device, const char *pName)
 {
 	auto deviceFunction = deviceFunctionPointers.find(std::string(pName));
diff --git a/src/Vulkan/VkGetProcAddress.hpp b/src/Vulkan/VkGetProcAddress.hpp
index 03c467f..49101ba 100644
--- a/src/Vulkan/VkGetProcAddress.hpp
+++ b/src/Vulkan/VkGetProcAddress.hpp
@@ -23,6 +23,7 @@
 class Instance;
 
 PFN_vkVoidFunction GetInstanceProcAddr(Instance *instance, const char *pName);
+PFN_vkVoidFunction GetPhysicalDeviceProcAddr(Instance *instance, const char *pName);
 PFN_vkVoidFunction GetDeviceProcAddr(Device *device, const char *pName);
 
 }  // namespace vk
diff --git a/src/Vulkan/android_host_vk_swiftshader.lds b/src/Vulkan/android_host_vk_swiftshader.lds
index d9663ed..7318bbb 100644
--- a/src/Vulkan/android_host_vk_swiftshader.lds
+++ b/src/Vulkan/android_host_vk_swiftshader.lds
@@ -3,6 +3,7 @@
 	# Loader-ICD interface functions
 	vk_icdGetInstanceProcAddr;
 	vk_icdNegotiateLoaderICDInterfaceVersion;
+	vk_icdGetPhysicalDeviceProcAddr;
 
 	# Vulkan 1.0 API entry functions
 	vkCreateInstance;
diff --git a/src/Vulkan/android_vk_swiftshader.lds b/src/Vulkan/android_vk_swiftshader.lds
index be404d9..b88bad3 100644
--- a/src/Vulkan/android_vk_swiftshader.lds
+++ b/src/Vulkan/android_vk_swiftshader.lds
@@ -4,6 +4,7 @@
 	# Loader-ICD interface functions
 	vk_icdGetInstanceProcAddr;
 	vk_icdNegotiateLoaderICDInterfaceVersion;
+	vk_icdGetPhysicalDeviceProcAddr;
 
 	# Android HAL module info object
 	HMI;
diff --git a/src/Vulkan/fuchsia_vk_swiftshader.lds b/src/Vulkan/fuchsia_vk_swiftshader.lds
index 2237ffb..c4f4464 100644
--- a/src/Vulkan/fuchsia_vk_swiftshader.lds
+++ b/src/Vulkan/fuchsia_vk_swiftshader.lds
@@ -6,6 +6,7 @@
 	# Loader-ICD interface functions
 	vk_icdGetInstanceProcAddr;
 	vk_icdNegotiateLoaderICDInterfaceVersion;
+	vk_icdGetPhysicalDeviceProcAddr;
 	vk_icdInitializeConnectToServiceCallback;
 
 local:
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 5360f95..275cf88 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -96,6 +96,7 @@
 
 #include <algorithm>
 #include <cinttypes>
+#include <cmath>
 #include <cstring>
 #include <functional>
 #include <map>
@@ -236,6 +237,11 @@
 	}
 }
 
+// This variable will be set to the negotiated ICD interface version negotiated with the loader.
+// It defaults to 1 because if vk_icdNegotiateLoaderICDInterfaceVersion is never called it means
+// that the loader doens't support version 2 of that interface.
+uint32_t sICDInterfaceVersion = 1;
+
 }  // namespace
 
 extern "C" {
@@ -248,10 +254,30 @@
 
 VK_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vk_icdNegotiateLoaderICDInterfaceVersion(uint32_t *pSupportedVersion)
 {
-	*pSupportedVersion = 3;
+	sICDInterfaceVersion = std::min(*pSupportedVersion, 7u);
+	*pSupportedVersion = sICDInterfaceVersion;
 	return VK_SUCCESS;
 }
 
+VK_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_icdGetPhysicalDeviceProcAddr(VkInstance instance, const char *pName)
+{
+	return vk::GetPhysicalDeviceProcAddr(vk::Cast(instance), pName);
+}
+
+#if VK_USE_PLATFORM_WIN32_KHR
+
+VKAPI_ATTR VkResult VKAPI_CALL vk_icdEnumerateAdapterPhysicalDevices(VkInstance instance, LUID adapterLUID, uint32_t *pPhysicalDeviceCount, VkPhysicalDevice *pPhysicalDevices)
+{
+	if(!pPhysicalDevices)
+	{
+		*pPhysicalDeviceCount = 0;
+	}
+
+	return VK_SUCCESS;
+}
+
+#endif  // VK_USE_PLATFORM_WIN32_KHR
+
 #if VK_USE_PLATFORM_FUCHSIA
 
 // This symbol must be exported by a Fuchsia Vulkan ICD. The Vulkan loader will
@@ -520,6 +546,39 @@
 
 	initializeLibrary();
 
+	// ICD interface rule for version 5 of the interface:
+	//    - If the loader supports version 4 or lower, the driver must fail with
+	//      VK_ERROR_INCOMPATIBLE_DRIVER for all vkCreateInstance calls with apiVersion
+	//      set to > Vulkan 1.0
+	//    - If the loader supports version 5 or above, the loader must fail with
+	//      VK_ERROR_INCOMPATIBLE_DRIVER if it can't handle the apiVersion, and drivers
+	//      should fail with VK_ERROR_INCOMPATIBLE_DRIVER only if they can not support the
+	//      specified apiVersion.
+	if(pCreateInfo->pApplicationInfo)
+	{
+		uint32_t appApiVersion = pCreateInfo->pApplicationInfo->apiVersion;
+		if(sICDInterfaceVersion <= 4)
+		{
+			// Any version above 1.0 is an error.
+			if(VK_API_VERSION_MAJOR(appApiVersion) != 1 || VK_API_VERSION_MINOR(appApiVersion) != 0)
+			{
+				return VK_ERROR_INCOMPATIBLE_DRIVER;
+			}
+		}
+		else
+		{
+			if(VK_API_VERSION_MAJOR(appApiVersion) > VK_API_VERSION_MINOR(vk::API_VERSION))
+			{
+				return VK_ERROR_INCOMPATIBLE_DRIVER;
+			}
+			if((VK_API_VERSION_MAJOR(appApiVersion) == VK_API_VERSION_MINOR(vk::API_VERSION)) &&
+			   VK_API_VERSION_MINOR(appApiVersion) > VK_API_VERSION_MINOR(vk::API_VERSION))
+			{
+				return VK_ERROR_INCOMPATIBLE_DRIVER;
+			}
+		}
+	}
+
 	if(pCreateInfo->flags != 0)
 	{
 		// Vulkan 1.3: "flags is reserved for future use." "flags must be 0"
diff --git a/src/Vulkan/vk_swiftshader.def b/src/Vulkan/vk_swiftshader.def
index 27354ef..3e3aca4f 100644
--- a/src/Vulkan/vk_swiftshader.def
+++ b/src/Vulkan/vk_swiftshader.def
@@ -3,6 +3,8 @@
 	; Loader-ICD interface functions

 	vk_icdGetInstanceProcAddr

 	vk_icdNegotiateLoaderICDInterfaceVersion

+	vk_icdGetPhysicalDeviceProcAddr

+	vk_icdEnumerateAdapterPhysicalDevices

 

 	; Vulkan 1.0 API entry functions

 	vkCreateInstance

diff --git a/src/Vulkan/vk_swiftshader.exports b/src/Vulkan/vk_swiftshader.exports
index 1153f00..68819a3 100644
--- a/src/Vulkan/vk_swiftshader.exports
+++ b/src/Vulkan/vk_swiftshader.exports
@@ -3,6 +3,7 @@
 # Loader-ICD interface functions
 _vk_icdGetInstanceProcAddr
 _vk_icdNegotiateLoaderICDInterfaceVersion
+_vk_icdGetPhysicalDeviceProcAddr
 
 # Type-strings and type-infos required by sanitizers
 _ZTS*
diff --git a/src/Vulkan/vk_swiftshader.lds b/src/Vulkan/vk_swiftshader.lds
index 873e9cd..38ed734 100644
--- a/src/Vulkan/vk_swiftshader.lds
+++ b/src/Vulkan/vk_swiftshader.lds
@@ -3,6 +3,7 @@
 	# Loader-ICD interface functions
 	vk_icdGetInstanceProcAddr;
 	vk_icdNegotiateLoaderICDInterfaceVersion;
+	vk_icdGetPhysicalDeviceProcAddr;
 
 	# Vulkan 1.0 API entry functions
 	vkCreateInstance;