Skip to content

Commit f077d87

Browse files
committed
fix(isaac): workaround for compatibility issues between isaacsim 5.1 and driver>590.48.01
1 parent 5e8a710 commit f077d87

4 files changed

Lines changed: 345 additions & 0 deletions

File tree

python/bricksim/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ def main():
113113
kit_args.append(" ".join(shlex.quote(arg) for arg in exec_args))
114114

115115
sys.argv = [sys.argv[0], *kit_args]
116+
import isaacsim_rtx_compat # noqa: F401
117+
116118
_uncache_bricksim_package()
117119
import isaacsim
118120

python/bricksim/cli/assemble_brick_expert.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ def main() -> int:
6565
Returns:
6666
Process exit code.
6767
"""
68+
import isaacsim_rtx_compat # noqa: F401, I001
69+
6870
from isaaclab.app import AppLauncher
6971

7072
parser = build_argument_parser()

python/isaacsim_rtx_compat.py

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
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()

python/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def run(self):
5151

5252
setup(
5353
packages=find_namespace_packages(where=".", include=["bricksim*"]),
54+
py_modules=["isaacsim_rtx_compat"],
5455
ext_modules=(
5556
[]
5657
if os.environ.get("BRICKSIM_SKIP_NATIVE_BUILD") == "1"

0 commit comments

Comments
 (0)