# pylint: disable=wildcard-import, unused-wildcard-import, invalid-name # Shit python (?) module that will likely crash the Lutris client. # We should not be doing that we live a life of shame knowing we are shipping this code # to you. We are terribly sorry and know we should do better but right now, you get what you get. # If you want a life without suffering, you can run Lutris with LUTRIS_NO_VKQUERY=1 # Vulkan detection by Patryk Obara (@dreamer) """Query Vulkan capabilities""" from collections import namedtuple # Standard Library from ctypes import ( CDLL, POINTER, Structure, byref, c_char, c_char_p, c_float, c_int32, c_size_t, c_uint8, c_uint32, c_uint64, c_void_p, pointer, ) from lutris.util import cache_single VkResult = c_int32 # enum (size == 4) VK_SUCCESS = 0 VK_ERROR_INITIALIZATION_FAILED = -3 VkStructureType = c_int32 # enum (size == 4) VkBool32 = c_uint32 VK_STRUCTURE_TYPE_APPLICATION_INFO = 0 VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1 VK_MAX_PHYSICAL_DEVICE_NAME_SIZE = 256 VK_UUID_SIZE = 16 VkInstanceCreateFlags = c_int32 # enum (size == 4) VkPhysicalDeviceType = c_int32 # enum (size == 4) VK_PHYSICAL_DEVICE_TYPE_CPU = 4 VkSampleCountFlags = c_int32 # enum (size == 4) VkInstance = c_void_p # handle (struct ptr) VkPhysicalDevice = c_void_p # handle (struct ptr) VkDeviceSize = c_uint64 DeviceInfo = namedtuple("DeviceInfo", "name api_version") def vk_make_version(major, minor, patch): """ VK_MAKE_VERSION macro logic for Python https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#fundamentals-versionnum """ return (major << 22) | (minor << 12) | patch def vk_api_version_major(version): return (version >> 22) & 0x7F def vk_api_version_minor(version): return (version >> 12) & 0x3FF def vk_api_version_patch(version): return version & 0xFFF class VkApplicationInfo(Structure): """Python shim for struct VkApplicationInfo https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkApplicationInfo.html """ # pylint: disable=too-few-public-methods _fields_ = [ ("sType", VkStructureType), ("pNext", c_void_p), ("pApplicationName", c_char_p), ("applicationVersion", c_uint32), ("pEngineName", c_char_p), ("engineVersion", c_uint32), ("apiVersion", c_uint32), ] def __init__(self, name, version): super().__init__() self.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO self.pApplicationName = name.encode() self.applicationVersion = c_uint32(vk_make_version(*version)) self.apiVersion = c_uint32(vk_make_version(1, 0, 0)) class VkInstanceCreateInfo(Structure): """Python shim for struct VkInstanceCreateInfo https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkInstanceCreateInfo.html """ # pylint: disable=too-few-public-methods _fields_ = [ ("sType", VkStructureType), ("pNext", c_void_p), ("flags", VkInstanceCreateFlags), ("pApplicationInfo", POINTER(VkApplicationInfo)), ("enabledLayerCount", c_uint32), ("ppEnabledLayerNames", c_char_p), ("enabledExtensionCount", c_uint32), ("ppEnabledExtensionNames", c_char_p), ] def __init__(self, app_info): super().__init__() self.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO self.pApplicationInfo = pointer(app_info) class VkPhysicalDeviceLimits(Structure): _fields_ = [ ("maxImageDimension1D", c_uint32), ("maxImageDimension2D", c_uint32), ("maxImageDimension3D", c_uint32), ("maxImageDimensionCube", c_uint32), ("maxImageArrayLayers", c_uint32), ("maxTexelBufferElements", c_uint32), ("maxUniformBufferRange", c_uint32), ("maxStorageBufferRange", c_uint32), ("maxPushConstantsSize", c_uint32), ("maxMemoryAllocationCount", c_uint32), ("maxSamplerAllocationCount", c_uint32), ("bufferImageGranularity", VkDeviceSize), ("sparseAddressSpaceSize", VkDeviceSize), ("maxBoundDescriptorSets", c_uint32), ("maxPerStageDescriptorSamplers", c_uint32), ("maxPerStageDescriptorUniformBuffers", c_uint32), ("maxPerStageDescriptorStorageBuffers", c_uint32), ("maxPerStageDescriptorSampledImages", c_uint32), ("maxPerStageDescriptorStorageImages", c_uint32), ("maxPerStageDescriptorInputAttachments", c_uint32), ("maxPerStageResources", c_uint32), ("maxDescriptorSetSamplers", c_uint32), ("maxDescriptorSetUniformBuffers", c_uint32), ("maxDescriptorSetUniformBuffersDynamic", c_uint32), ("maxDescriptorSetStorageBuffers", c_uint32), ("maxDescriptorSetStorageBuffersDynamic", c_uint32), ("maxDescriptorSetSampledImages", c_uint32), ("maxDescriptorSetStorageImages", c_uint32), ("maxDescriptorSetInputAttachments", c_uint32), ("maxVertexInputAttributes", c_uint32), ("maxVertexInputBindings", c_uint32), ("maxVertexInputAttributeOffset", c_uint32), ("maxVertexInputBindingStride", c_uint32), ("maxVertexOutputComponents", c_uint32), ("maxTessellationGenerationLevel", c_uint32), ("maxTessellationPatchSize", c_uint32), ("maxTessellationControlPerVertexInputComponents", c_uint32), ("maxTessellationControlPerVertexOutputComponents", c_uint32), ("maxTessellationControlPerPatchOutputComponents", c_uint32), ("maxTessellationControlTotalOutputComponents", c_uint32), ("maxTessellationEvaluationInputComponents", c_uint32), ("maxTessellationEvaluationOutputComponents", c_uint32), ("maxGeometryShaderInvocations", c_uint32), ("maxGeometryInputComponents", c_uint32), ("maxGeometryOutputComponents", c_uint32), ("maxGeometryOutputVertices", c_uint32), ("maxGeometryTotalOutputComponents", c_uint32), ("maxFragmentInputComponents", c_uint32), ("maxFragmentOutputAttachments", c_uint32), ("maxFragmentDualSrcAttachments", c_uint32), ("maxFragmentCombinedOutputResources", c_uint32), ("maxComputeSharedMemorySize", c_uint32), ("maxComputeWorkGroupCount", c_uint32 * 3), ("maxComputeWorkGroupInvocations", c_uint32), ("maxComputeWorkGroupSize", c_uint32 * 3), ("subPixelPrecisionBits", c_uint32), ("subTexelPrecisionBits", c_uint32), ("mipmapPrecisionBits", c_uint32), ("maxDrawIndexedIndexValue", c_uint32), ("maxDrawIndirectCount", c_uint32), ("maxSamplerLodBias", c_float), ("maxSamplerAnisotropy", c_float), ("maxViewports", c_uint32), ("maxViewportDimensions", c_uint32 * 2), ("viewportBoundsRange", c_float * 2), ("viewportSubPixelBits", c_uint32), ("minMemoryMapAlignment", c_size_t), ("minTexelBufferOffsetAlignment", VkDeviceSize), ("minUniformBufferOffsetAlignment", VkDeviceSize), ("minStorageBufferOffsetAlignment", VkDeviceSize), ("minTexelOffset", c_int32), ("maxTexelOffset", c_uint32), ("minTexelGatherOffset", c_int32), ("maxTexelGatherOffset", c_uint32), ("minInterpolationOffset", c_float), ("maxInterpolationOffset", c_float), ("subPixelInterpolationOffsetBits", c_uint32), ("maxFrameBufferWidth", c_uint32), ("maxFrameBufferHeight", c_uint32), ("maxFrameBufferLayers", c_uint32), ("frameBufferColorSampleCounts", VkSampleCountFlags), ("frameBufferDepthSampleCounts", VkSampleCountFlags), ("frameBufferStencilSampleCounts", VkSampleCountFlags), ("frameBufferNoAttachmentsSampleCounts", VkSampleCountFlags), ("maxColorAttachments", c_uint32), ("sampledImageColorSampleCounts", VkSampleCountFlags), ("sampledImageIntegerSampleCounts", VkSampleCountFlags), ("sampledImageDepthSampleCounts", VkSampleCountFlags), ("sampledImageStencilSampleCounts", VkSampleCountFlags), ("storageImageSampleCounts", VkSampleCountFlags), ("maxSampleMaskWords", c_uint32), ("timestampComputeAndGraphics", VkBool32), ("timestampPeriod", c_float), ("maxClipDistances", c_uint32), ("maxCullDistances", c_uint32), ("maxCombinedClipAndCullDistances", c_uint32), ("discreteQueuePriorities", c_uint32), ("pointSizeRange", c_float * 2), ("lineWidthRange", c_float * 2), ("pointSizeGranularity", c_float), ("lineWidthGranularity", c_float), ("strictLines", VkBool32), ("standardSampleLocations", VkBool32), ("optimalBufferCopyOffsetAlignment", VkDeviceSize), ("optimalBufferCopyRowPitchAlignment", VkDeviceSize), ("nonCoherentAtomSize", VkDeviceSize), ] class VkPhysicalDeviceSparseProperties(Structure): _fields_ = [ ("residencyStandard2DBlockShape", VkBool32), ("residencyStandard2DMultisampleBlockShape", VkBool32), ("residencyStandard3DBlockShape", VkBool32), ("residencyAlignedMipSize", VkBool32), ("residencyNonResidentStrict", VkBool32), ] class VkPhysicalDeviceProperties(Structure): _fields_ = [ ("apiVersion", c_uint32), ("driverVersion", c_uint32), ("vendorID", c_uint32), ("deviceID", c_uint32), ("deviceType", VkPhysicalDeviceType), ("deviceName", c_char * VK_MAX_PHYSICAL_DEVICE_NAME_SIZE), ("pipelineCacheUUID", c_uint8 * VK_UUID_SIZE), ("limits", VkPhysicalDeviceLimits), ("sparseProperties", VkPhysicalDeviceSparseProperties), ] @cache_single def is_vulkan_supported() -> bool: """ Returns True iff vulkan library can be loaded, initialized, and reports at least one physical device available. """ try: vulkan = _get_vulkan() except OSError: return False instance = _get_vk_instance() if not instance: return False dev_count = c_uint32(0) result = vulkan.vkEnumeratePhysicalDevices(instance, byref(dev_count), None) return result == VK_SUCCESS and dev_count.value > 0 @cache_single def get_vulkan_api_version(): """ Queries libvulkan to get the API version; if this library is missing it returns None. Returns an encoded Vulkan version integer; use vk_api_version_major() and like methods to parse it. """ try: vulkan = _get_vulkan() except OSError: return None try: enumerate_instance_version = vulkan.vkEnumerateInstanceVersion except AttributeError: # Vulkan 1.0 did not have vkEnumerateInstanceVersion at all! return vk_make_version(1, 0, 0) version = c_uint32(0) result = enumerate_instance_version(byref(version)) return version.value if result == VK_SUCCESS else None def get_device_info(): """ Returns a list of the physical devices known to Vulkan, represented as (name, api_version) named-tuples and the api_version numbers are encoded, so use vk_api_version_major() and friends to parse them. They are sorted so the highest version device is first, and software rendering devices are omitted. """ try: vulkan = _get_vulkan() except OSError: return [] instance = _get_vk_instance() if not instance: return [] dev_count = c_uint32(0) result = vulkan.vkEnumeratePhysicalDevices(instance, byref(dev_count), None) if result != VK_SUCCESS or dev_count.value <= 0: return [] devices = (VkPhysicalDevice * dev_count.value)() result = vulkan.vkEnumeratePhysicalDevices(instance, byref(dev_count), devices) if result != VK_SUCCESS: return [] device_info = [] for physical_device in devices: dev_props = VkPhysicalDeviceProperties() vulkan.vkGetPhysicalDeviceProperties(physical_device, byref(dev_props)) if dev_props.deviceType != VK_PHYSICAL_DEVICE_TYPE_CPU: name = dev_props.deviceName.decode("utf-8") device_info.append(DeviceInfo(name, dev_props.apiVersion)) return sorted(device_info, key=lambda t: t.api_version, reverse=True) @cache_single def get_expected_api_version(): """Returns the version tuple of the API version we expect to have; it is the least of the Vulkan library API version, and the best device's API version.""" api_version = get_vulkan_api_version() if not api_version: return None devices = get_device_info() if devices: return min(api_version, devices[0].api_version) return api_version def format_version(version): if version: major = vk_api_version_major(version) minor = vk_api_version_minor(version) patch = vk_api_version_patch(version) return "%s.%s.%s" % (major, minor, patch) return "(none)" @cache_single def _get_vk_instance(): """Returns our VKInstance, or None if it can't be obtained. We've had user see crashes when destroying this, so we don't- we allocate it on demand and keep it for the lifetime of Lutris.""" try: vulkan = _get_vulkan() except OSError: return [] app_info = VkApplicationInfo("vkinfo", version=(0, 1, 0)) create_info = VkInstanceCreateInfo(app_info) instance = VkInstance() result = vulkan.vkCreateInstance(byref(create_info), 0, byref(instance)) if result != VK_SUCCESS: return None return instance def _get_vulkan(): vulkan = CDLL("libvulkan.so.1") # Provide function signatures; this is required on platforms where var-args are # passed in some other way than normal args, and is safer in general. vkCreateInstance = vulkan.vkCreateInstance vkCreateInstance.restype = VkResult vkCreateInstance.argtypes = [POINTER(VkInstanceCreateInfo), c_void_p, POINTER(VkInstance)] vkEnumeratePhysicalDevices = vulkan.vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices.restype = VkResult vkEnumeratePhysicalDevices.argtypes = [VkInstance, POINTER(c_uint32), POINTER(VkPhysicalDevice)] vkGetPhysicalDeviceProperties = vulkan.vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties.restype = None vkGetPhysicalDeviceProperties.argtypes = [VkPhysicalDevice, POINTER(VkPhysicalDeviceProperties)] vkEnumerateInstanceVersion = vulkan.vkEnumerateInstanceVersion vkEnumerateInstanceVersion.restype = VkResult vkEnumerateInstanceVersion.argtypes = [POINTER(c_uint32)] return vulkan