diff --git a/AUTHORS b/AUTHORS index f717c7e98..eca9af2b9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,3 +76,4 @@ Contributors: Ludovic Soulié Yunusemre Şentürk Yurii Kolesnykov + Patryk Obara (@dreamer) diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index b0847c885..e6c66f292 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -7,10 +7,10 @@ import shutil from lutris import runtime from lutris.gui.dialogs import FileDialog from lutris.runners.runner import Runner -from lutris.util import datapath, display, dxvk, system, vulkan +from lutris.util import datapath, display, dxvk, system from lutris.util.log import logger from lutris.util.strings import parse_version -from lutris.util.vulkan import vulkan_available +from lutris.util.vkquery import is_vulkan_supported from lutris.util.wineprefix import WinePrefixManager from lutris.util.x360ce import X360ce from lutris.util.wine import ( @@ -179,9 +179,8 @@ class wine(Runner): return True def dxvk_vulkan_callback(config): - result = vulkan.vulkan_check() - if result != vulkan_available.ALL: - if not display_vulkan_error(result, False): + if not is_vulkan_supported(): + if not display_vulkan_error(False): return False return True @@ -729,9 +728,8 @@ class wine(Runner): using_dxvk = self.runner_config.get('dxvk') if using_dxvk: - result = vulkan.vulkan_check() - if result != vulkan_available.ALL: - if not display_vulkan_error(result, True): + if not is_vulkan_supported(): + if not display_vulkan_error(True): return {'error': 'VULKAN_NOT_FOUND'} if not system.path_exists(game_exe): diff --git a/lutris/util/vkquery.py b/lutris/util/vkquery.py new file mode 100644 index 000000000..9d2a29a93 --- /dev/null +++ b/lutris/util/vkquery.py @@ -0,0 +1,97 @@ +# pylint: disable=wildcard-import, unused-wildcard-import, invalid-name +# Vulkan detection by Patryk Obara (@dreamer) +"""Query Vulkan capabilities""" + +from ctypes import c_int32, c_uint32, c_void_p, c_char_p, Structure, POINTER, pointer, CDLL, byref + +VkResult = c_int32 # enum (size == 4) +VK_SUCCESS = 0 +VK_ERROR_INITIALIZATION_FAILED = -3 + +VkStructureType = c_int32 # enum (size == 4) +VK_STRUCTURE_TYPE_APPLICATION_INFO = 0 +VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1 + +VkInstanceCreateFlags = c_int32 # enum (size == 4) + +VkInstance = c_void_p # handle (struct ptr) + + +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 c_uint32((major << 22) | (minor << 12) | patch) + + +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 = vk_make_version(*version) + self.apiVersion = 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) + + +def is_vulkan_supported(): + """ + Returns True iff vulkan library can be loaded, initialized, + and reports at least one physical device available. + """ + vulkan = None + try: + vulkan = CDLL('libvulkan.so.1') + except OSError: + return False + 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 False + dev_count = c_uint32(0) + result = vulkan.vkEnumeratePhysicalDevices(instance, byref(dev_count), 0) + vulkan.vkDestroyInstance(instance, 0) + return result == VK_SUCCESS and dev_count.value > 0 diff --git a/lutris/util/vulkan.py b/lutris/util/vulkan.py deleted file mode 100644 index 79eaff70f..000000000 --- a/lutris/util/vulkan.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Vulkan helper module""" -import subprocess -from enum import Enum - - -class vulkan_available(Enum): - NONE = 0 - THIRTY_TWO = 1 - SIXTY_FOUR = 2 - ALL = 3 - - -def vulkan_check(): - has_64_bit = False - has_32_bit = False - for line in subprocess.check_output(["ldconfig", "-p"]).splitlines(): - line = str(line) - if 'libvulkan' in line: - if 'x86-64' in line: - has_64_bit = True - else: - has_32_bit = True - - if not (has_64_bit or has_32_bit): - return vulkan_available.NONE - if has_64_bit and not has_32_bit: - return vulkan_available.SIXTY_FOUR - if not has_64_bit and has_32_bit: - return vulkan_available.THIRTY_TWO - return vulkan_available.ALL diff --git a/lutris/util/wine.py b/lutris/util/wine.py index 2002c18ab..abe58ffa6 100644 --- a/lutris/util/wine.py +++ b/lutris/util/wine.py @@ -9,7 +9,6 @@ from lutris.gui.dialogs import DontShowAgainDialog, ErrorDialog from lutris.util import system from lutris.util.log import logger from lutris.util.strings import version_sort -from lutris.util.vulkan import vulkan_available from lutris.runners.steam import steam MIN_NUMBER_FILES_OPEN = 1048576 @@ -239,14 +238,7 @@ def get_real_executable(windows_executable, working_dir=None): return (windows_executable, [], working_dir) -def display_vulkan_error(option, on_launch): - if option == vulkan_available.NONE: - message = "No Vulkan loader was detected." - if option == vulkan_available.SIXTY_FOUR: - message = "32-bit Vulkan loader was not detected." - if option == vulkan_available.THIRTY_TWO: - message = "64-bit Vulkan loader was not detected." - +def display_vulkan_error(on_launch): if on_launch: checkbox_message = "Launch anyway and do not show this message again." else: @@ -254,7 +246,7 @@ def display_vulkan_error(option, on_launch): setting = 'hide-no-vulkan-warning' DontShowAgainDialog(setting, - message, + "No Vulkan loader was detected.", secondary_message="Please follow the installation " "procedures as described in\n" ""