Support version 7 of the ICD interface.

Reland after making the version 5 requirement only apply if the Vulkan
loader loaded Swiftshader (and not when an application, like Chromium,
loads vkGetInstanceProcAddr directly).

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: Idb5b7bd473a13f5ab3c2319174f80a220599b237
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/72568
Tested-by: Corentin Wallez <cwallez@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Corentin Wallez <cwallez@google.com>
Kokoro-Result: kokoro <noreply+kokoro@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..626499e 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,22 +237,56 @@
 	}
 }
 
+// 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;
+// Whether any vk_icd* entrypoints were used. This is used to distinguish between applications that
+// use the Vulkan loader to load Swiftshader (in which case vk_icd functions are called), and
+// applications that load Swiftshader and grab vkGetInstanceProcAddr directly.
+bool sICDEntryPointsUsed = false;
+
 }  // namespace
 
 extern "C" {
 VK_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_icdGetInstanceProcAddr(VkInstance instance, const char *pName)
 {
 	TRACE("(VkInstance instance = %p, const char* pName = %p)", instance, pName);
+	sICDEntryPointsUsed = true;
 
 	return vk::GetInstanceProcAddr(vk::Cast(instance), pName);
 }
 
 VK_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vk_icdNegotiateLoaderICDInterfaceVersion(uint32_t *pSupportedVersion)
 {
-	*pSupportedVersion = 3;
+	sICDEntryPointsUsed = true;
+
+	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)
+{
+	sICDEntryPointsUsed = true;
+	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)
+{
+	sICDEntryPointsUsed = true;
+	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
@@ -266,6 +301,7 @@
     PFN_vkConnectToService callback)
 {
 	TRACE("(callback = %p)", callback);
+	sICDEntryPointsUsed = true;
 	vk::icdFuchsiaServiceConnectCallback = callback;
 	return VK_SUCCESS;
 }
@@ -520,6 +556,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(sICDEntryPointsUsed && 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;