Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions src/rdc/daemon_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,35 @@ def _cleanup_temp(state: DaemonState) -> None:
_log = logging.getLogger("rdc.daemon")


def _match_capture_gpu(cap: Any, sd: Any = None) -> Any | None:
"""Find the GPU used for capture by matching structured data against available GPUs."""
try:
gpus = cap.GetAvailableGPUs()
if not gpus:
return None
if len(gpus) == 1:
return gpus[0]
if sd is None:
return gpus[0]
for i in range(len(sd.chunks)):
c = sd.chunks[i]
if c.name == "vkEnumeratePhysicalDevices":
for j in range(c.NumChildren()):
child = c.GetChild(j)
if child.name == "physProps":
for k in range(child.NumChildren()):
prop = child.GetChild(k)
if prop.name == "deviceName":
name = prop.AsString()
for g in gpus:
if g.name == name:
return g
break
except Exception: # noqa: BLE001
pass
return gpus[0] if gpus else None


def _load_replay(state: DaemonState) -> str | None:
"""Load renderdoc module and open capture. Returns error string or None."""
from rdc.discover import find_renderdoc
Expand All @@ -189,7 +218,13 @@ def _load_replay(state: DaemonState) -> str | None:
cap.Shutdown()
return "local replay not supported on this platform"

result, controller = cap.OpenCapture(rd.ReplayOptions(), None)
opts = rd.ReplayOptions()
gpu = _match_capture_gpu(cap, cap.GetStructuredData())
if gpu is not None:
opts.forceGPUVendor = gpu.vendor
opts.forceGPUDeviceID = gpu.deviceID
_log.info("replay GPU: %s (vendor=%d id=%d)", gpu.name, gpu.vendor, gpu.deviceID)
result, controller = cap.OpenCapture(opts, None)
if result != rd.ResultCode.Succeeded:
cap.Shutdown()
return f"OpenCapture failed: {result}"
Expand Down Expand Up @@ -321,8 +356,18 @@ def _load_remote_replay(state: DaemonState, remote_url: str) -> str | None:
state.local_capture_path = str(local_tmp)
state.local_capture_is_temp = True

remote_opts = rd.ReplayOptions()
if state.local_capture_path:
tmp_cap = rd.OpenCaptureFile()
if tmp_cap.OpenFile(state.local_capture_path, "", None) == rd.ResultCode.Succeeded:
gpu = _match_capture_gpu(tmp_cap)
if gpu is not None:
remote_opts.forceGPUVendor = gpu.vendor
remote_opts.forceGPUDeviceID = gpu.deviceID
tmp_cap.Shutdown()

result, controller = remote.OpenCapture(
rd.RemoteServer.NoPreference, remote_path, rd.ReplayOptions(), None
rd.RemoteServer.NoPreference, remote_path, remote_opts, None
)
if result != rd.ResultCode.Succeeded:
_cleanup_temp_capture(state)
Expand Down
11 changes: 7 additions & 4 deletions src/rdc/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ def _get_diagnostic() -> ProbeOutcome | None:


def _is_arm_studio_dir(directory: str) -> bool:
"""Return True if directory contains ARM PS patched renderdoc.so + librenderdoc.so."""
"""Return True if directory is an ARM Performance Studio renderdoc install."""
d = Path(directory)
return (d / "librenderdoc.so").is_file() and (d / "renderdoc.so").is_file()
if not ((d / "librenderdoc.so").is_file() and (d / "renderdoc.so").is_file()):
return False
parts = d.resolve().parts
return any("arm-performance-studio" in p.lower() for p in parts)


def _preload_librenderdoc(directory: str) -> None:
Expand Down Expand Up @@ -136,7 +139,7 @@ def find_renderdoc() -> ModuleType | None:

env_path = os.environ.get("RENDERDOC_PYTHON_PATH")
if env_path:
candidates.append(env_path)
candidates.append(os.path.abspath(env_path))

try:
candidates.extend(_platform.renderdoc_search_paths())
Expand Down Expand Up @@ -203,7 +206,7 @@ def find_renderdoccmd() -> Path | None:
env_path = os.environ.get("RENDERDOC_PYTHON_PATH")
if env_path:
name = "renderdoccmd.exe" if sys.platform == "win32" else "renderdoccmd"
candidate = Path(env_path) / name
candidate = Path(os.path.abspath(env_path)) / name
if candidate.exists():
return candidate

Expand Down
4 changes: 3 additions & 1 deletion tests/mocks/mock_renderdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1824,7 +1824,9 @@ def CreateHeadlessWindowingData(width: int, height: int) -> Any:


class ReplayOptions:
pass
forceGPUVendor: int = 0
forceGPUDeviceID: int = 0
forceGPUDriverName: str = ""


def ExecuteAndInject(
Expand Down
9 changes: 8 additions & 1 deletion tests/unit/test_discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,16 @@ class TestArmStudioDir:
"""_is_arm_studio_dir detects ARM PS directory layout."""

def test_both_files_present(self, tmp_path: Path) -> None:
arm_dir = tmp_path / "arm-performance-studio" / "renderdoc" / "lib"
arm_dir.mkdir(parents=True)
(arm_dir / "librenderdoc.so").write_text("fake")
(arm_dir / "renderdoc.so").write_text("fake")
assert _is_arm_studio_dir(str(arm_dir)) is True

def test_non_arm_dir_with_both_files(self, tmp_path: Path) -> None:
(tmp_path / "librenderdoc.so").write_text("fake")
(tmp_path / "renderdoc.so").write_text("fake")
assert _is_arm_studio_dir(str(tmp_path)) is True
assert _is_arm_studio_dir(str(tmp_path)) is False

def test_missing_librenderdoc(self, tmp_path: Path) -> None:
(tmp_path / "renderdoc.so").write_text("fake")
Expand Down
Loading