|
| 1 | +"""Enable a generated Vulkan profile before application startup. |
| 2 | +
|
| 3 | +Recent NVIDIA drivers can report |
| 4 | +``VkPhysicalDeviceMaintenance3Properties.maxMemoryAllocationSize`` as |
| 5 | +``UINT64_MAX`` where the same GPU previously reported a finite value just below |
| 6 | +4 GiB. Some RTX initialization paths treat that value as a practical allocation |
| 7 | +limit when sizing acceleration-structure resources, so the sentinel value can |
| 8 | +push closed-source renderer code into an invalid allocation path before the app |
| 9 | +finishes startup. |
| 10 | +
|
| 11 | +This module must be imported before the target application initializes Vulkan. |
| 12 | +It queries the local NVIDIA device, computes a conservative finite cap, downloads |
| 13 | +and verifies the Khronos Vulkan Profiles layer into XDG cache when needed, then |
| 14 | +generates a cache-local profile and implicit-layer manifest. The profile changes |
| 15 | +only the reported ``maxMemoryAllocationSize`` property; renderer settings, |
| 16 | +features, and application arguments are left unchanged. The profile is enabled |
| 17 | +only for NVIDIA Vulkan driver versions newer than 590.48.01 that also report an |
| 18 | +unsafe allocation limit; older or already-safe drivers are left untouched. |
| 19 | +""" |
| 20 | + |
| 21 | +from ctypes import ( |
| 22 | + CDLL, |
| 23 | + POINTER, |
| 24 | + Structure, |
| 25 | + addressof, |
| 26 | + byref, |
| 27 | + c_char, |
| 28 | + c_char_p, |
| 29 | + c_int32, |
| 30 | + c_uint8, |
| 31 | + c_uint32, |
| 32 | + c_uint64, |
| 33 | + c_void_p, |
| 34 | +) |
| 35 | +from hashlib import sha256 |
| 36 | +from json import dump |
| 37 | +from os import environ |
| 38 | +from pathlib import Path |
| 39 | +from shutil import which |
| 40 | +from subprocess import run |
| 41 | +from sys import stderr |
| 42 | +from urllib.request import urlretrieve |
| 43 | + |
| 44 | +_URL = "https://geo.mirror.pkgbuild.com/extra/os/x86_64/vulkan-profiles-1.4.341.0-1-x86_64.pkg.tar.zst" |
| 45 | +_PKG = "d52d72259b6c8911c9e3b41985e014cb76954f013b87af45f5ef1fcff1126131" |
| 46 | +_SO = "99f20d7af6e073eac74a04308be01a3a81505629cff6dc066a7d3282b4dce533" |
| 47 | +_NAME = "VP_RTX_driver_compat_generated" |
| 48 | +_NVIDIA = 0x10DE |
| 49 | +_LAST_KNOWN_GOOD_DRIVER = (590, 48, 1, 0) |
| 50 | +_TWO_MIB = 2 * 1024 * 1024 |
| 51 | +_CAP = 4 * 1024 * 1024 * 1024 - _TWO_MIB |
| 52 | +_CACHE = ( |
| 53 | + Path(environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) / "vulkan-rtx-compat" |
| 54 | +) |
| 55 | + |
| 56 | + |
| 57 | +class _App(Structure): |
| 58 | + _fields_ = [ |
| 59 | + ("sType", c_uint32), |
| 60 | + ("pNext", c_void_p), |
| 61 | + ("pApplicationName", c_char_p), |
| 62 | + ("applicationVersion", c_uint32), |
| 63 | + ("pEngineName", c_char_p), |
| 64 | + ("engineVersion", c_uint32), |
| 65 | + ("apiVersion", c_uint32), |
| 66 | + ] |
| 67 | + |
| 68 | + |
| 69 | +class _Create(Structure): |
| 70 | + _fields_ = [ |
| 71 | + ("sType", c_uint32), |
| 72 | + ("pNext", c_void_p), |
| 73 | + ("flags", c_uint32), |
| 74 | + ("pApplicationInfo", c_void_p), |
| 75 | + ("enabledLayerCount", c_uint32), |
| 76 | + ("ppEnabledLayerNames", c_void_p), |
| 77 | + ("enabledExtensionCount", c_uint32), |
| 78 | + ("ppEnabledExtensionNames", c_void_p), |
| 79 | + ] |
| 80 | + |
| 81 | + |
| 82 | +class _Props(Structure): |
| 83 | + _fields_ = [ |
| 84 | + ("apiVersion", c_uint32), |
| 85 | + ("driverVersion", c_uint32), |
| 86 | + ("vendorID", c_uint32), |
| 87 | + ("deviceID", c_uint32), |
| 88 | + ("deviceType", c_uint32), |
| 89 | + ("deviceName", c_char * 256), |
| 90 | + ("pipelineCacheUUID", c_uint8 * 16), |
| 91 | + ("_rest", c_uint8 * 2048), |
| 92 | + ] |
| 93 | + |
| 94 | + |
| 95 | +class _Props2(Structure): |
| 96 | + _fields_ = [("sType", c_uint32), ("pNext", c_void_p), ("properties", _Props)] |
| 97 | + |
| 98 | + |
| 99 | +class _Maintenance3(Structure): |
| 100 | + _fields_ = [ |
| 101 | + ("sType", c_uint32), |
| 102 | + ("pNext", c_void_p), |
| 103 | + ("maxPerSetDescriptors", c_uint32), |
| 104 | + ("maxMemoryAllocationSize", c_uint64), |
| 105 | + ] |
| 106 | + |
| 107 | + |
| 108 | +class _MemoryType(Structure): |
| 109 | + _fields_ = [("propertyFlags", c_uint32), ("heapIndex", c_uint32)] |
| 110 | + |
| 111 | + |
| 112 | +class _MemoryHeap(Structure): |
| 113 | + _fields_ = [("size", c_uint64), ("flags", c_uint32)] |
| 114 | + |
| 115 | + |
| 116 | +class _MemoryProps(Structure): |
| 117 | + _fields_ = [ |
| 118 | + ("memoryTypeCount", c_uint32), |
| 119 | + ("memoryTypes", _MemoryType * 32), |
| 120 | + ("memoryHeapCount", c_uint32), |
| 121 | + ("memoryHeaps", _MemoryHeap * 16), |
| 122 | + ] |
| 123 | + |
| 124 | + |
| 125 | +def _hash(path: Path) -> str: |
| 126 | + data = sha256() |
| 127 | + with path.open("rb") as stream: |
| 128 | + for chunk in iter(lambda: stream.read(1024 * 1024), b""): |
| 129 | + data.update(chunk) |
| 130 | + return data.hexdigest() |
| 131 | + |
| 132 | + |
| 133 | +def _prepend(name: str, value: Path) -> None: |
| 134 | + text = str(value) |
| 135 | + parts = environ.get(name, "").split(":") if environ.get(name) else [] |
| 136 | + if text not in parts: |
| 137 | + environ[name] = f"{text}:{environ[name]}" if environ.get(name) else text |
| 138 | + |
| 139 | + |
| 140 | +def _layer() -> Path: |
| 141 | + layer = _CACHE / "libVkLayer_khronos_profiles.so" |
| 142 | + if layer.exists() and _hash(layer) == _SO: |
| 143 | + return layer |
| 144 | + force_download = layer.exists() |
| 145 | + if which("tar") is None: |
| 146 | + msg = "tar is required to extract the Vulkan profiles layer" |
| 147 | + raise RuntimeError(msg) |
| 148 | + _CACHE.mkdir(parents=True, exist_ok=True) |
| 149 | + package = _CACHE / "vulkan-profiles-1.4.341.0-1-x86_64.pkg.tar.zst" |
| 150 | + if force_download: |
| 151 | + package.unlink(missing_ok=True) |
| 152 | + if not package.exists() or _hash(package) != _PKG: |
| 153 | + tmp = package.with_suffix(".download") |
| 154 | + urlretrieve(_URL, tmp) |
| 155 | + if _hash(tmp) != _PKG: |
| 156 | + tmp.unlink(missing_ok=True) |
| 157 | + msg = "downloaded Vulkan profiles package failed sha256 verification" |
| 158 | + raise RuntimeError(msg) |
| 159 | + tmp.replace(package) |
| 160 | + extracted = run( |
| 161 | + [ |
| 162 | + "tar", |
| 163 | + "--zstd", |
| 164 | + "-xOf", |
| 165 | + str(package), |
| 166 | + "usr/lib/libVkLayer_khronos_profiles.so", |
| 167 | + ], |
| 168 | + check=True, |
| 169 | + capture_output=True, |
| 170 | + ).stdout |
| 171 | + layer.write_bytes(extracted) |
| 172 | + layer.chmod(0o755) |
| 173 | + if _hash(layer) != _SO: |
| 174 | + layer.unlink(missing_ok=True) |
| 175 | + msg = "extracted Vulkan profiles layer failed sha256 verification" |
| 176 | + raise RuntimeError(msg) |
| 177 | + return layer |
| 178 | + |
| 179 | + |
| 180 | +def _limit() -> int | None: |
| 181 | + override = environ.get("RTX_VULKAN_COMPAT_MAX_MEMORY_ALLOCATION_SIZE") |
| 182 | + if override is not None: |
| 183 | + return int(override, 0) |
| 184 | + vk = CDLL("libvulkan.so.1") |
| 185 | + create_instance = vk.vkCreateInstance |
| 186 | + create_instance.argtypes = [POINTER(_Create), c_void_p, POINTER(c_void_p)] |
| 187 | + create_instance.restype = c_int32 |
| 188 | + enumerate_devices = vk.vkEnumeratePhysicalDevices |
| 189 | + enumerate_devices.argtypes = [c_void_p, POINTER(c_uint32), POINTER(c_void_p)] |
| 190 | + enumerate_devices.restype = c_int32 |
| 191 | + get_props = vk.vkGetPhysicalDeviceProperties2 |
| 192 | + get_props.argtypes = [c_void_p, POINTER(_Props2)] |
| 193 | + get_memory = vk.vkGetPhysicalDeviceMemoryProperties |
| 194 | + get_memory.argtypes = [c_void_p, POINTER(_MemoryProps)] |
| 195 | + destroy_instance = vk.vkDestroyInstance |
| 196 | + destroy_instance.argtypes = [c_void_p, c_void_p] |
| 197 | + app = _App( |
| 198 | + sType=0, pApplicationName=b"vulkan-rtx-compat", apiVersion=(1 << 22) | (1 << 12) |
| 199 | + ) |
| 200 | + info = _Create(sType=1, pApplicationInfo=c_void_p(addressof(app))) |
| 201 | + instance = c_void_p() |
| 202 | + if create_instance(byref(info), None, byref(instance)) != 0: |
| 203 | + return None |
| 204 | + caps: list[int] = [] |
| 205 | + try: |
| 206 | + count = c_uint32() |
| 207 | + if enumerate_devices(instance, byref(count), None) != 0 or count.value == 0: |
| 208 | + return None |
| 209 | + devices = (c_void_p * count.value)() |
| 210 | + if enumerate_devices(instance, byref(count), devices) != 0: |
| 211 | + return None |
| 212 | + for device in devices: |
| 213 | + m3 = _Maintenance3(sType=1000168000) |
| 214 | + props = _Props2(sType=1000059001, pNext=c_void_p(addressof(m3))) |
| 215 | + get_props(device, byref(props)) |
| 216 | + driver = int(props.properties.driverVersion) |
| 217 | + if ( |
| 218 | + props.properties.vendorID != _NVIDIA |
| 219 | + or ( |
| 220 | + driver >> 22, |
| 221 | + (driver >> 14) & 0xFF, |
| 222 | + (driver >> 6) & 0xFF, |
| 223 | + driver & 0x3F, |
| 224 | + ) |
| 225 | + <= _LAST_KNOWN_GOOD_DRIVER |
| 226 | + or int(m3.maxMemoryAllocationSize) <= _CAP |
| 227 | + ): |
| 228 | + continue |
| 229 | + mem = _MemoryProps() |
| 230 | + get_memory(device, byref(mem)) |
| 231 | + heap = max( |
| 232 | + ( |
| 233 | + int(mem.memoryHeaps[i].size) |
| 234 | + for i in range(mem.memoryHeapCount) |
| 235 | + if mem.memoryHeaps[i].flags & 1 |
| 236 | + ), |
| 237 | + default=0, |
| 238 | + ) |
| 239 | + caps.append(min(_CAP, heap - _TWO_MIB) if heap > _TWO_MIB else _CAP) |
| 240 | + finally: |
| 241 | + destroy_instance(instance, None) |
| 242 | + return ((min(caps) // 256) * 256) if caps else None |
| 243 | + |
| 244 | + |
| 245 | +def enable() -> bool: |
| 246 | + """Enable the compatibility profile when the current Vulkan device needs it. |
| 247 | +
|
| 248 | + Returns: |
| 249 | + True if the profile layer was enabled, otherwise False. |
| 250 | + """ |
| 251 | + if environ.get("RTX_VULKAN_COMPAT_ACTIVE") == "1": |
| 252 | + return True |
| 253 | + if environ.get("RTX_VULKAN_COMPAT_DISABLE") == "1": |
| 254 | + return False |
| 255 | + limit = _limit() |
| 256 | + if limit is None or limit <= 0: |
| 257 | + return False |
| 258 | + layer = _layer() |
| 259 | + profile_dir = _CACHE / "profiles" |
| 260 | + data_root = _CACHE / "xdg-data" |
| 261 | + manifest_dir = data_root / "vulkan" / "implicit_layer.d" |
| 262 | + profile_dir.mkdir(parents=True, exist_ok=True) |
| 263 | + manifest_dir.mkdir(parents=True, exist_ok=True) |
| 264 | + with (profile_dir / f"{_NAME}.json").open("w", encoding="utf-8") as stream: |
| 265 | + dump( |
| 266 | + { |
| 267 | + "$schema": "https://schema.khronos.org/vulkan/profiles-0.8-latest.json#", |
| 268 | + "capabilities": { |
| 269 | + "RTX_DRIVER_COMPAT": { |
| 270 | + "properties": { |
| 271 | + "VkPhysicalDeviceMaintenance3Properties": { |
| 272 | + "maxMemoryAllocationSize": limit |
| 273 | + } |
| 274 | + } |
| 275 | + } |
| 276 | + }, |
| 277 | + "profiles": { |
| 278 | + _NAME: { |
| 279 | + "version": 1, |
| 280 | + "api-version": "1.1.0", |
| 281 | + "label": "RTX Driver Compatibility", |
| 282 | + "description": "Generated compatibility profile.", |
| 283 | + "contributors": { |
| 284 | + "local": {"github": "https://github.com/yushijinhun"} |
| 285 | + }, |
| 286 | + "history": [ |
| 287 | + { |
| 288 | + "revision": 1, |
| 289 | + "date": "2026-05-10", |
| 290 | + "author": "local", |
| 291 | + "comment": "Generated compatibility profile.", |
| 292 | + } |
| 293 | + ], |
| 294 | + "capabilities": ["RTX_DRIVER_COMPAT"], |
| 295 | + } |
| 296 | + }, |
| 297 | + }, |
| 298 | + stream, |
| 299 | + indent=2, |
| 300 | + ) |
| 301 | + stream.write("\n") |
| 302 | + with (manifest_dir / "VkLayer_KHRONOS_profiles.json").open( |
| 303 | + "w", encoding="utf-8" |
| 304 | + ) as stream: |
| 305 | + dump( |
| 306 | + { |
| 307 | + "file_format_version": "1.2.1", |
| 308 | + "layer": { |
| 309 | + "name": "VK_LAYER_KHRONOS_profiles", |
| 310 | + "type": "GLOBAL", |
| 311 | + "library_path": str(layer), |
| 312 | + "api_version": "1.4.341", |
| 313 | + "implementation_version": "1", |
| 314 | + "description": "Khronos Profiles layer", |
| 315 | + "enable_environment": {"RTX_VULKAN_COMPAT_ENABLE_LAYER": "1"}, |
| 316 | + "disable_environment": {"RTX_VULKAN_COMPAT_DISABLE_LAYER": ""}, |
| 317 | + }, |
| 318 | + }, |
| 319 | + stream, |
| 320 | + indent=2, |
| 321 | + ) |
| 322 | + stream.write("\n") |
| 323 | + environ["RTX_VULKAN_COMPAT_ENABLE_LAYER"] = "1" |
| 324 | + environ["VK_KHRONOS_PROFILES_PROFILE_NAME"] = _NAME |
| 325 | + environ["VK_KHRONOS_PROFILES_SIMULATE_CAPABILITIES"] = "SIMULATE_PROPERTIES_BIT" |
| 326 | + environ["VK_KHRONOS_PROFILES_DEBUG_REPORTS"] = "DEBUG_REPORT_ERROR_BIT" |
| 327 | + environ["RTX_VULKAN_COMPAT_ACTIVE"] = "1" |
| 328 | + environ["RTX_VULKAN_COMPAT_MAX_MEMORY_ALLOCATION_SIZE_ACTIVE"] = str(limit) |
| 329 | + _prepend("VK_KHRONOS_PROFILES_PROFILE_DIRS", profile_dir) |
| 330 | + _prepend("VK_ADD_IMPLICIT_LAYER_PATH", manifest_dir) |
| 331 | + _prepend("XDG_DATA_DIRS", data_root) |
| 332 | + print( |
| 333 | + "RTX Vulkan compatibility profile enabled: " |
| 334 | + f"maxMemoryAllocationSize={limit}, cache={_CACHE}", |
| 335 | + file=stderr, |
| 336 | + ) |
| 337 | + return True |
| 338 | + |
| 339 | + |
| 340 | +enabled = enable() |
0 commit comments