// // Copyright 2020 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // SystemInfo_vulkan.cpp: Generic vulkan implementation of SystemInfo.h // TODO: Use VK_KHR_driver_properties. http://anglebug.com/42263671 #include "gpu_info_util/SystemInfo_vulkan.h" #include "common/unsafe_buffers.h" #include #include "gpu_info_util/SystemInfo_internal.h" #include #include #include #include #include #include "common/angleutils.h" #include "common/debug.h" #include "common/platform_helpers.h" #include "common/system_utils.h" #include "common/vulkan/libvulkan_loader.h" namespace angle { namespace { // Note: most drivers use VK_MAKE_API_VERSION to create the version. VersionInfo ParseGenericDriverVersion(uint32_t driverVersion) { VersionInfo version = {}; version.major = VK_API_VERSION_MAJOR(driverVersion); version.minor = VK_API_VERSION_MINOR(driverVersion); version.subMinor = VK_API_VERSION_PATCH(driverVersion); return version; } } // namespace VersionInfo ParseAMDVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseArmVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseBroadcomVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseSwiftShaderVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseImaginationVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseIntelWindowsVulkanDriverVersion(uint32_t driverVersion) { VersionInfo version = {}; // Windows Intel driver versions are built in the following format: // // Major (18 bits) | Minor (14 bits) // version.major = driverVersion >> 14; version.minor = driverVersion & 0x3FFF; return version; } VersionInfo ParseKazanVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseNvidiaVulkanDriverVersion(uint32_t driverVersion) { VersionInfo version = {}; version.major = driverVersion >> 22; version.minor = driverVersion >> 14 & 0xFF; version.subMinor = driverVersion >> 6 & 0xFF; version.patch = driverVersion & 0x3F; return version; } VersionInfo ParseQualcommVulkanDriverVersion(uint32_t driverVersion) { VersionInfo version = {}; if ((driverVersion & 0x80000000) != 0) { // The major version of the new QCOM drivers seem to be 512. However, the value parsed from // the physical device properties shows this field as 0. version = ParseGenericDriverVersion(driverVersion); version.major = 512; return version; } // Older drivers with an unknown format, consider them version 0. version.minor = driverVersion; return version; } VersionInfo ParseSamsungVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseVeriSiliconVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseVivanteVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseMesaVulkanDriverVersion(uint32_t driverVersion) { return ParseGenericDriverVersion(driverVersion); } VersionInfo ParseMoltenVulkanDriverVersion(uint32_t driverVersion) { // Note: MoltenVK formulates its version number as a decimal number like so: // (major * 10000) + (minor * 100) + patch VersionInfo version = {}; version.major = driverVersion / 10000; version.minor = (driverVersion / 100) % 100; version.patch = driverVersion % 100; return version; } VKAPI_ATTR VkBool32 VKAPI_CALL VVLDebugUtilsMessenger(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT *callbackData, void *userData) { // VUID-VkDebugUtilsMessengerCallbackDataEXT-pMessage-parameter // pMessage must be a null-terminated UTF-8 string ASSERT(callbackData->pMessage != nullptr); // Log the validation error message std::ostringstream log; if (callbackData->pMessageIdName != nullptr) { log << "[ " << callbackData->pMessageIdName << " ] "; } log << callbackData->pMessage << std::endl; std::string msg = log.str(); WARN() << msg; bool triggerAssert = (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0; if (triggerAssert) { // Trigger assert when there is validation error, so we can catch it on bots. ASSERT(false); } return VK_FALSE; } constexpr const char *kVkKhronosValidationLayerName[] = {"VK_LAYER_KHRONOS_validation"}; bool HasKhronosValidationLayer(const std::vector &layerProps) { for (const auto &layerProp : layerProps) { ASSERT(strlen(layerProp.layerName) < sizeof(layerProp.layerName)); if (ANGLE_UNSAFE_TODO(strncmp(layerProp.layerName, kVkKhronosValidationLayerName[0], sizeof(layerProp.layerName))) == 0) { return true; } } WARN() << "Vulkan validation layers are missing"; return false; } class VulkanLibrary final : NonCopyable { public: VulkanLibrary() = default; ~VulkanLibrary() { if (mDebugUtilsMessenger) { mPfnDestroyDebugUtilsMessengerEXT(mInstance, mDebugUtilsMessenger, nullptr); } if (mInstance != VK_NULL_HANDLE) { mPfnDestroyInstance(mInstance, nullptr); } CloseSystemLibrary(mLibVulkan); } std::vector GetInstanceExtensionNames() const { std::vector extensionNames; if (!mPfnEnumerateInstanceExtensionProperties) { return extensionNames; } uint32_t extensionCount = 0; if (mPfnEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr) != VK_SUCCESS) { return extensionNames; } std::vector extensions(extensionCount); if (mPfnEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()) != VK_SUCCESS) { return extensionNames; } for (const auto &extension : extensions) { extensionNames.emplace_back(extension.extensionName); } std::sort(extensionNames.begin(), extensionNames.end()); return extensionNames; } bool ExtensionFound(std::string const &needle, const std::vector &haystack) { // NOTE: The list must be sorted. return std::binary_search(haystack.begin(), haystack.end(), needle); } VkInstance getVulkanInstance() { mLibVulkan = vk::OpenLibVulkan(); if (!mLibVulkan) { // If Vulkan doesn't exist, bail-out early: return VK_NULL_HANDLE; } mPfnGetInstanceProcAddr = getProcWithDLSym("vkGetInstanceProcAddr"); if (!mPfnGetInstanceProcAddr) { return VK_NULL_HANDLE; } mPfnCreateInstance = getProcWithDLSym("vkCreateInstance"); if (!mPfnCreateInstance) { return VK_NULL_HANDLE; } mPfnEnumerateInstanceLayerProperties = getProcWithDLSym( "vkEnumerateInstanceLayerProperties"); if (!mPfnEnumerateInstanceLayerProperties) { return VK_NULL_HANDLE; } mPfnEnumerateInstanceExtensionProperties = getProcWithDLSym( "vkEnumerateInstanceExtensionProperties"); if (!mPfnEnumerateInstanceExtensionProperties) { return VK_NULL_HANDLE; } // Determine the available Vulkan instance version: uint32_t instanceVersion = VK_API_VERSION_1_0; #if defined(VK_VERSION_1_1) PFN_vkEnumerateInstanceVersion pfnEnumerateInstanceVersion = getProcWithDLSym("vkEnumerateInstanceVersion"); if (!pfnEnumerateInstanceVersion || pfnEnumerateInstanceVersion(&instanceVersion) != VK_SUCCESS) { instanceVersion = VK_API_VERSION_1_0; } #endif // VK_VERSION_1_1 std::vector availableInstanceExtensions = GetInstanceExtensionNames(); std::vector enabledInstanceExtensions; bool hasPortabilityEnumeration = false; if (IsApple() && ExtensionFound(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, availableInstanceExtensions)) { // On iOS/macOS, there is no native Vulkan driver, so we need to // enable the portability enumeration extension to allow use of // MoltenVK. enabledInstanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); hasPortabilityEnumeration = true; } // Enable vulkan validation layer bool enableValidationLayer = false; // Only enable validation layer when asserts are enabled #if !defined(NDEBUG) || defined(ANGLE_ASSERT_ALWAYS_ON) uint32_t instanceLayerCount = 0; { mPfnEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr); } std::vector instanceLayerProps(instanceLayerCount); if (instanceLayerCount > 0) { mPfnEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayerProps.data()); } enableValidationLayer = HasKhronosValidationLayer(instanceLayerProps); #endif bool hasDebugMessengerExtension = false; if (enableValidationLayer && ExtensionFound(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, availableInstanceExtensions)) { enabledInstanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); hasDebugMessengerExtension = true; } // Create a Vulkan instance: VkApplicationInfo appInfo; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pNext = nullptr; appInfo.pApplicationName = ""; appInfo.applicationVersion = 1; appInfo.pEngineName = ""; appInfo.engineVersion = 1; appInfo.apiVersion = instanceVersion; VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; VkInstanceCreateInfo createInstanceInfo{}; createInstanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; if (enableValidationLayer) { createInstanceInfo.enabledLayerCount = 1; createInstanceInfo.ppEnabledLayerNames = kVkKhronosValidationLayerName; if (hasDebugMessengerExtension) { constexpr VkDebugUtilsMessageSeverityFlagsEXT kSeveritiesToLog = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; constexpr VkDebugUtilsMessageTypeFlagsEXT kMessagesToLog = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; debugCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; debugCreateInfo.messageSeverity = kSeveritiesToLog; debugCreateInfo.messageType = kMessagesToLog; debugCreateInfo.pfnUserCallback = &VVLDebugUtilsMessenger; debugCreateInfo.pUserData = nullptr; createInstanceInfo.pNext = &debugCreateInfo; } } createInstanceInfo.pApplicationInfo = &appInfo; createInstanceInfo.enabledExtensionCount = static_cast(enabledInstanceExtensions.size()); createInstanceInfo.ppEnabledExtensionNames = enabledInstanceExtensions.empty() ? nullptr : enabledInstanceExtensions.data(); if (hasPortabilityEnumeration) { createInstanceInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; } if (mPfnCreateInstance(&createInstanceInfo, nullptr, &mInstance) != VK_SUCCESS) { return VK_NULL_HANDLE; } mPfnDestroyInstance = getProcWithDLSym("vkDestroyInstance"); if (!mPfnDestroyInstance) { return VK_NULL_HANDLE; } mPfnEnumeratePhysicalDevices = getProcWithDLSym("vkEnumeratePhysicalDevices"); if (!mPfnEnumeratePhysicalDevices) { return VK_NULL_HANDLE; } mPfnGetPhysicalDeviceProperties = getProcWithDLSym("vkGetPhysicalDeviceProperties"); if (!mPfnGetPhysicalDeviceProperties) { return VK_NULL_HANDLE; } // Even then vkEnumerateInstanceVersion() returns VK_API_VERSION_1_1 or higher, // mPfnGetPhysicalDeviceProperties2 can be still be nullptr if the actual vulkan device // doesn't support VK_API_VERSION_1_1. // Caller needs to check VkPhysicalDeviceProperties.apiVersion >= VK_API_VERSION_1_1 before // trying to access mPfnGetPhysicalDeviceProperties2. mPfnGetPhysicalDeviceProperties2 = getProcWithDLSym("vkGetPhysicalDeviceProperties2"); if (hasDebugMessengerExtension) { mPfnCreateDebugUtilsMessengerEXT = getProc("vkCreateDebugUtilsMessengerEXT"); mPfnDestroyDebugUtilsMessengerEXT = getProc("vkDestroyDebugUtilsMessengerEXT"); } // Set up vulkan validation layer debug messenger to relay the VVL error to the callback // function VVLDebugUtilsMessenger. hasDebugMessengerExtension = hasDebugMessengerExtension && mPfnCreateDebugUtilsMessengerEXT && mPfnDestroyDebugUtilsMessengerEXT; if (hasDebugMessengerExtension) { ASSERT(debugCreateInfo.sType == VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT); mPfnCreateDebugUtilsMessengerEXT(mInstance, &debugCreateInfo, nullptr, &mDebugUtilsMessenger); } return mInstance; } template Func getProcWithDLSym(const char *fn) const { return reinterpret_cast(angle::GetLibrarySymbol(mLibVulkan, fn)); } template Func getProc(const char *fn) const { if (mInstance == VK_NULL_HANDLE) { return (Func)mPfnGetInstanceProcAddr(NULL, fn); } else { return (Func)mPfnGetInstanceProcAddr(mInstance, fn); } } PFN_vkEnumeratePhysicalDevices getEnumeratePhysicalDevicesFunc() { return mPfnEnumeratePhysicalDevices; } PFN_vkGetPhysicalDeviceProperties getPhysicalDevicePropertiesFunc() { return mPfnGetPhysicalDeviceProperties; } PFN_vkGetPhysicalDeviceProperties2 getPhysicalDeviceProperties2Func() { return mPfnGetPhysicalDeviceProperties2; } private: void *mLibVulkan = nullptr; VkInstance mInstance = VK_NULL_HANDLE; VkDebugUtilsMessengerEXT mDebugUtilsMessenger = VK_NULL_HANDLE; PFN_vkGetInstanceProcAddr mPfnGetInstanceProcAddr = nullptr; PFN_vkCreateInstance mPfnCreateInstance = nullptr; PFN_vkDestroyInstance mPfnDestroyInstance = nullptr; PFN_vkEnumerateInstanceLayerProperties mPfnEnumerateInstanceLayerProperties = nullptr; PFN_vkEnumerateInstanceExtensionProperties mPfnEnumerateInstanceExtensionProperties = nullptr; PFN_vkEnumeratePhysicalDevices mPfnEnumeratePhysicalDevices = nullptr; PFN_vkGetPhysicalDeviceProperties mPfnGetPhysicalDeviceProperties = nullptr; PFN_vkGetPhysicalDeviceProperties2 mPfnGetPhysicalDeviceProperties2 = nullptr; PFN_vkCreateDebugUtilsMessengerEXT mPfnCreateDebugUtilsMessengerEXT = nullptr; PFN_vkDestroyDebugUtilsMessengerEXT mPfnDestroyDebugUtilsMessengerEXT = nullptr; }; ANGLE_FORMAT_PRINTF(1, 2) std::string FormatString(const char *fmt, ...) { va_list vararg; va_start(vararg, fmt); std::vector buffer; size_t len = ANGLE_UNSAFE_TODO(FormatStringIntoVector(fmt, vararg, buffer)); va_end(vararg); return std::string(&buffer[0], len); } bool GetSystemInfoVulkan(SystemInfo *info) { return GetSystemInfoVulkanWithICD(info, vk::ICD::Default); } bool GetSystemInfoVulkanWithICD(SystemInfo *info, vk::ICD preferredICD) { const bool enableValidationLayers = false; vk::ScopedVkLoaderEnvironment scopedEnvironment(enableValidationLayers, preferredICD); static_assert(sizeof(GPUDeviceInfo::deviceUUID) == VK_UUID_SIZE); // This implementation builds on top of the Vulkan API, but cannot assume the existence of the // Vulkan library. ANGLE can be installed on versions of Android as old as Ice Cream Sandwich. // Therefore, we need to use dlopen()/dlsym() in order to see if Vulkan is installed on the // system, and if so, to use it: VulkanLibrary vkLibrary; VkInstance instance = vkLibrary.getVulkanInstance(); if (instance == VK_NULL_HANDLE) { // If Vulkan doesn't exist, bail-out early: return false; } // Enumerate the Vulkan physical devices, which are ANGLE gpus: auto pfnEnumeratePhysicalDevices = vkLibrary.getEnumeratePhysicalDevicesFunc(); auto pfnGetPhysicalDeviceProperties = vkLibrary.getPhysicalDevicePropertiesFunc(); auto pfnGetPhysicalDeviceProperties2 = vkLibrary.getPhysicalDeviceProperties2Func(); uint32_t physicalDeviceCount = 0; if (pfnEnumeratePhysicalDevices(instance, &physicalDeviceCount, nullptr) != VK_SUCCESS) { return false; } std::vector physicalDevices(physicalDeviceCount); if (pfnEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices.data()) != VK_SUCCESS) { return false; } // If we get to here, we will likely provide a valid answer (unless an unknown vendorID): info->gpus.resize(physicalDeviceCount); for (uint32_t i = 0; i < physicalDeviceCount; i++) { VkPhysicalDeviceDriverProperties driverProperties = {}; driverProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; VkPhysicalDeviceIDProperties deviceIDProperties = {}; deviceIDProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES; deviceIDProperties.pNext = &driverProperties; VkPhysicalDeviceProperties2 properties2 = {}; properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; properties2.pNext = &deviceIDProperties; VkPhysicalDeviceProperties &properties = properties2.properties; pfnGetPhysicalDeviceProperties(physicalDevices[i], &properties); // vkGetPhysicalDeviceProperties2() is supported since 1.1 // Use vkGetPhysicalDeviceProperties2() to get driver information. if (properties.apiVersion >= VK_API_VERSION_1_1) { pfnGetPhysicalDeviceProperties2(physicalDevices[i], &properties2); } // Fill in data for a given physical device (a.k.a. gpu): GPUDeviceInfo &gpu = info->gpus[i]; gpu.vendorId = properties.vendorID; gpu.deviceId = properties.deviceID; gpu.deviceName = properties.deviceName; ANGLE_UNSAFE_TODO(memcpy(gpu.deviceUUID, deviceIDProperties.deviceUUID, VK_UUID_SIZE)); ANGLE_UNSAFE_TODO(memcpy(gpu.driverUUID, deviceIDProperties.driverUUID, VK_UUID_SIZE)); // TODO(http://anglebug.com/42266143): Use driverID instead of the hardware vendorID to // detect driveVendor, etc. switch (properties.vendorID) { case kVendorID_AMD: gpu.driverVendor = "Advanced Micro Devices, Inc"; gpu.detailedDriverVersion = ParseAMDVulkanDriverVersion(properties.driverVersion); break; case kVendorID_ARM: gpu.driverVendor = "Arm Holdings"; gpu.detailedDriverVersion = ParseArmVulkanDriverVersion(properties.driverVersion); break; case kVendorID_Broadcom: gpu.driverVendor = "Broadcom"; gpu.detailedDriverVersion = ParseBroadcomVulkanDriverVersion(properties.driverVersion); break; case kVendorID_GOOGLE: gpu.driverVendor = "Google"; gpu.detailedDriverVersion = ParseSwiftShaderVulkanDriverVersion(properties.driverVersion); break; case kVendorID_ImgTec: gpu.driverVendor = "Imagination Technologies Limited"; gpu.detailedDriverVersion = ParseImaginationVulkanDriverVersion(properties.driverVersion); break; case kVendorID_Intel: gpu.driverVendor = "Intel Corporation"; if (IsWindows()) { gpu.detailedDriverVersion = ParseIntelWindowsVulkanDriverVersion(properties.driverVersion); } else { gpu.detailedDriverVersion = ParseMesaVulkanDriverVersion(properties.driverVersion); } break; case kVendorID_Kazan: gpu.driverVendor = "Kazan Software"; gpu.detailedDriverVersion = ParseKazanVulkanDriverVersion(properties.driverVersion); break; case kVendorID_NVIDIA: gpu.driverVendor = "NVIDIA Corporation"; gpu.detailedDriverVersion = ParseNvidiaVulkanDriverVersion(properties.driverVersion); break; case kVendorID_Qualcomm: case kVendorID_Qualcomm_DXGI: gpu.driverVendor = "Qualcomm Technologies, Inc"; gpu.detailedDriverVersion = ParseQualcommVulkanDriverVersion(properties.driverVersion); break; case kVendorID_Samsung: gpu.driverVendor = "Samsung"; gpu.detailedDriverVersion = ParseSamsungVulkanDriverVersion(properties.driverVersion); break; case kVendorID_VeriSilicon: gpu.driverVendor = "VeriSilicon"; gpu.detailedDriverVersion = ParseVeriSiliconVulkanDriverVersion(properties.driverVersion); break; case kVendorID_Vivante: gpu.driverVendor = "Vivante"; gpu.detailedDriverVersion = ParseVivanteVulkanDriverVersion(properties.driverVersion); break; case kVendorID_Mesa: gpu.driverVendor = "Mesa"; gpu.detailedDriverVersion = ParseMesaVulkanDriverVersion(properties.driverVersion); break; case kVendorID_Apple: // Note: This is MoltenVk gpu.driverVendor = "Apple"; gpu.detailedDriverVersion = ParseMoltenVulkanDriverVersion(properties.driverVersion); break; default: return false; } gpu.driverVersion = FormatString("%d.%d.%d", gpu.detailedDriverVersion.major, gpu.detailedDriverVersion.minor, gpu.detailedDriverVersion.subMinor); gpu.driverId = static_cast(driverProperties.driverID); gpu.driverApiVersion = properties.apiVersion; gpu.driverDate = ""; } return true; } } // namespace angle