mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
fix(preflight): require Vulkan 1.3 and timeline semaphores on the same device
vulkan_devices() flattened every apiVersion and check_vulkan() accepted any timelineSemaphore = true, so a host with an old fast device and a new device without timeline semaphores could pass without a usable device. Each GPUn block is now one record, and one device must report both. The full dump supplies the features and --summary the versions, matched by GPU index. A dump without feature sections still gets the version check, and a summary that lists no device is a required failure. Adds three tests: a second device satisfies both, requirements are not combined across devices, and no reported device.
This commit is contained in:
1 parent
c19c9ad47e
commit
6a00849048
3 files changed
+110
-29
No files matched your search
@@ -31,7 +31,7 @@ Use the [setup instructions](SETUP.md) from the repository root as your ordinary
|
||||
- **kernel** 5.14 or newer for `MADV_POPULATE_WRITE`.
|
||||
- **llvm** 16 or newer; 22 or 23 is what the patches are tested with. Iridium calls `LLVMGetVersion`, which LLVM 15 does not export, so an LLVM 15 runtime aborts as soon as Riot creates a Metal device ([#139](https://github.com/lucannez64/darling-lol/issues/139)). LLVM 16-21 builds with a warning.
|
||||
- **llvm-tools**: `llvm-objdump` and `llvm-nm`. They are now resolved as an unversioned name, as `llvm-<tool>-<N>`, or from `llvm-config --bindir`, so Ubuntu's `llvm-15-dev` no longer needs symlinks in `/usr/local/bin`.
|
||||
- **vulkan** API 1.3 and timeline semaphores. Indium rejects older devices, so this stops the setup with the version `vulkaninfo` reports. An installed `vulkaninfo` that fails or times out is also required to be fixed, because the loader or the ICD files are then broken; without `vulkaninfo` at all it only warns, because the tool may be missing even when the driver is fine.
|
||||
- **vulkan** API 1.3 and timeline semaphores. Indium rejects older devices, so this stops the setup unless one device reports both the version and the semaphores, as `vulkaninfo` shows per GPU. An installed `vulkaninfo` that fails or times out is also required to be fixed, because the loader or the ICD files are then broken; without `vulkaninfo` at all it only warns, because the tool may be missing even when the driver is fine.
|
||||
- **x11** only warns: `DISPLAY` is probed with `xdpyinfo`, and the GL/EGL vendor is reported when a probe is installed. An inaccessible display is what made `iokitd` restart in a loop before [#140](https://github.com/lucannez64/darling-lol/issues/140); export `XAUTHORITY` from servers and SSH sessions. A build without a display is still valid.
|
||||
|
||||
Run the same checks by hand, from the checkout:
|
||||
|
||||
+60
-17
@@ -149,15 +149,57 @@ def check_llvm_tools(report):
|
||||
'The runtime install validates Darwin binaries with them.')
|
||||
|
||||
|
||||
def gpu_blocks(text):
|
||||
"""{index: [lines]} for each `GPUn:` block in vulkaninfo output."""
|
||||
blocks = {}
|
||||
current = None
|
||||
for line in (text or '').splitlines():
|
||||
header = re.match(r'GPU(\d+):\s*$', line)
|
||||
if header:
|
||||
current = blocks.setdefault(int(header.group(1)), [])
|
||||
continue
|
||||
if current is not None:
|
||||
current.append(line)
|
||||
return blocks
|
||||
|
||||
|
||||
def first_match(pattern, lines):
|
||||
for line in lines:
|
||||
found = re.search(pattern, line)
|
||||
if found:
|
||||
return found.group(1).strip()
|
||||
return None
|
||||
|
||||
|
||||
def vulkan_devices():
|
||||
"""(highest apiVersion, device names) from vulkaninfo, or None when it is not available."""
|
||||
"""One record per Vulkan device, or None when vulkaninfo does not run.
|
||||
|
||||
A device is kept as a whole record so a host with an old fast GPU and a new
|
||||
GPU without timeline semaphores cannot pass by combining the two. The full
|
||||
dump carries the features; `--summary` carries the versions that every
|
||||
version of the tool prints.
|
||||
"""
|
||||
summary = output(['vulkaninfo', '--summary'])
|
||||
if summary is None:
|
||||
return None
|
||||
api = [version(found) for found in re.findall(r'apiVersion\s*=\s*(\d+\.\d+)', summary)]
|
||||
api = [value for value in api if value]
|
||||
names = re.findall(r'deviceName\s*=\s*(.+)', summary)
|
||||
return (max(api) if api else None, [name.strip() for name in names])
|
||||
features = gpu_blocks(output(['vulkaninfo'], timeout=60))
|
||||
devices = []
|
||||
for index, lines in sorted(gpu_blocks(summary).items()):
|
||||
found = features.get(index, [])
|
||||
values = [value == 'true'
|
||||
for value in re.findall(r'timelineSemaphore\s*=\s*(true|false)',
|
||||
'\n'.join(found))]
|
||||
devices.append({'name': first_match(r'deviceName\s*=\s*(.+)', lines),
|
||||
'api': version(first_match(r'apiVersion\s*=\s*(\d+\.\d+)', lines)),
|
||||
'timeline': any(values) if values else None})
|
||||
return devices
|
||||
|
||||
|
||||
def describe_device(device):
|
||||
text = f'{device["name"] or "unnamed device"} {dotted(device["api"])}'
|
||||
if device['timeline'] is not None:
|
||||
text += ', timelineSemaphore ' + ('true' if device['timeline'] else 'false')
|
||||
return text
|
||||
|
||||
|
||||
def check_vulkan(report):
|
||||
@@ -174,19 +216,20 @@ def check_vulkan(report):
|
||||
'timeline semaphores; without a suitable device the game cannot render.',
|
||||
required=False)
|
||||
return
|
||||
api, names = devices
|
||||
detail = f'apiVersion {dotted(api)}' + (f' ({", ".join(names[:2])})' if names else '')
|
||||
report('vulkan', api is not None and api >= MIN_VULKAN, detail,
|
||||
f'Vulkan {dotted(MIN_VULKAN)} or newer is required: Indium rejects older devices. '
|
||||
'Update Mesa or the vendor driver, or check `vulkaninfo --summary`.')
|
||||
feature = output(['vulkaninfo'], timeout=60)
|
||||
if feature is None:
|
||||
if not devices:
|
||||
report('vulkan', False, 'no devices reported',
|
||||
'`vulkaninfo --summary` listed no Vulkan device. Check the loader and the ICD '
|
||||
'files, or reinstall the driver: the game cannot render without a device.')
|
||||
return
|
||||
values = re.findall(r'timelineSemaphore\s*=\s*(true|false)', feature)
|
||||
if values and 'true' not in values:
|
||||
report('vulkan-timeline', False, 'timelineSemaphore = false',
|
||||
'No Vulkan device reports timeline semaphores, which the Metal wrapper needs; '
|
||||
'update the driver or select another ICD.')
|
||||
# A dump that carries no feature section still gets the version check, as before.
|
||||
checked = any(device['timeline'] is not None for device in devices)
|
||||
usable = [device for device in devices
|
||||
if device['api'] is not None and device['api'] >= MIN_VULKAN
|
||||
and (device['timeline'] or not checked)]
|
||||
report('vulkan', bool(usable), '; '.join(describe_device(device) for device in devices),
|
||||
f'Vulkan {dotted(MIN_VULKAN)} or newer and timeline semaphores on the same device are '
|
||||
'required: Indium rejects older devices and uses the semaphores. Update Mesa or the '
|
||||
'vendor driver, or check `vulkaninfo`.')
|
||||
|
||||
|
||||
def check_display(report):
|
||||
|
||||
+49
-11
@@ -40,12 +40,24 @@ esac'''.replace('LLVM_BINDIR', str(self.llvm_bindir)))
|
||||
self.stub('llvm-nm', 'exit 0')
|
||||
self.stub('cmake', 'echo "cmake version 3.31.6"')
|
||||
self.stub('git', 'case "$*" in "lfs version") echo "git-lfs/3.7.0 (GitHub; linux amd64)";; esac')
|
||||
self.stub('vulkaninfo', '''case "$*" in
|
||||
--summary) printf 'apiVersion = 1.3.289\\ndeviceName = Test GPU\\n';;
|
||||
*) printf 'timelineSemaphore = true\\n';;
|
||||
esac''')
|
||||
self.vulkan([('1.3.289', 'Test GPU', True)])
|
||||
self.stub('xdpyinfo', 'exit 0')
|
||||
|
||||
def vulkan(self, devices):
|
||||
"""Stub vulkaninfo with [(apiVersion, name, timeline)] per device."""
|
||||
summary = ['Devices:', '========']
|
||||
features = ['Device Properties and Extensions:', '=================================']
|
||||
for index, (api, name, timeline) in enumerate(devices):
|
||||
summary += [f'GPU{index}:', f'\tapiVersion = {api}', f'\tdeviceName = {name}']
|
||||
features += [f'GPU{index}:', 'VkPhysicalDeviceProperties:',
|
||||
f'\tapiVersion = {api}', f'\tdeviceName = {name}']
|
||||
if timeline is not None:
|
||||
features += ['VkPhysicalDeviceVulkan12Features:',
|
||||
f'\ttimelineSemaphore = {"true" if timeline else "false"}']
|
||||
self.stub('vulkaninfo', 'case "$*" in\n'
|
||||
" --summary) printf '%s\\n' " + ' '.join(f"'{line}'" for line in summary) + ';;\n'
|
||||
" *) printf '%s\\n' " + ' '.join(f"'{line}'" for line in features) + ';;\nesac')
|
||||
|
||||
def stub(self, name, body, folder=None):
|
||||
path = (folder or self.bin) / name
|
||||
path.write_text('#!/bin/sh\nset -eu\n' + body + '\n')
|
||||
@@ -152,11 +164,12 @@ esac''')
|
||||
|
||||
|
||||
def test_old_vulkan_stops_the_setup(self):
|
||||
self.stub('vulkaninfo', 'echo "apiVersion = 1.2.170"')
|
||||
self.vulkan([('1.2.170', 'Old GPU', True)])
|
||||
result = self.preflight()
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertIn('FAIL vulkan', result.stdout)
|
||||
self.assertIn('Vulkan 1.3 or newer is required', result.stdout)
|
||||
self.assertIn('Vulkan 1.3 or newer', result.stdout)
|
||||
self.assertIn('Old GPU 1.2', result.stdout)
|
||||
|
||||
|
||||
def test_missing_vulkaninfo_only_warns(self):
|
||||
@@ -178,13 +191,38 @@ esac''')
|
||||
|
||||
|
||||
def test_missing_timeline_semaphores_is_reported(self):
|
||||
self.stub('vulkaninfo', '''case "$*" in
|
||||
--summary) echo 'apiVersion = 1.3.289';;
|
||||
*) echo 'timelineSemaphore = false';;
|
||||
esac''')
|
||||
self.vulkan([('1.3.289', 'Test GPU', False)])
|
||||
result = self.preflight()
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertIn('FAIL vulkan-timeline', result.stdout)
|
||||
self.assertIn('FAIL vulkan', result.stdout)
|
||||
self.assertIn('timelineSemaphore false', result.stdout)
|
||||
self.assertIn('on the same device', result.stdout)
|
||||
|
||||
|
||||
def test_a_second_device_can_satisfy_both_requirements(self):
|
||||
self.vulkan([('1.4.309', 'New GPU without semaphores', False),
|
||||
('1.4.309', 'Second GPU', True)])
|
||||
result = self.preflight()
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertIn('ok vulkan', result.stdout)
|
||||
|
||||
|
||||
def test_requirements_are_not_combined_across_devices(self):
|
||||
self.vulkan([('1.2.170', 'Old GPU with semaphores', True),
|
||||
('1.4.309', 'New GPU without semaphores', False)])
|
||||
result = self.preflight()
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertIn('FAIL vulkan', result.stdout)
|
||||
self.assertIn('Old GPU with semaphores 1.2', result.stdout)
|
||||
self.assertIn('New GPU without semaphores 1.4', result.stdout)
|
||||
|
||||
|
||||
def test_no_reported_device_is_required(self):
|
||||
self.vulkan([])
|
||||
result = self.preflight()
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertIn('FAIL vulkan', result.stdout)
|
||||
self.assertIn('no devices reported', result.stdout)
|
||||
|
||||
|
||||
def test_unreachable_display_only_warns(self):
|
||||
|
||||
Reference in new issue
Block a user