From 1ff05e4e6fe3d538d60009dfc04e48f1cbf56091 Mon Sep 17 00:00:00 2001 From: vii Date: Tue, 13 Oct 2026 18:37:35 -0500 Subject: [PATCH] fix(vr): do not recover an XR session when the restarted runtime uses another GPU --- include/mocktail/vr/openxr_backend.h | 2 ++ src/vr/openxr_backend.cc | 30 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/mocktail/vr/openxr_backend.h b/include/mocktail/vr/openxr_backend.h index 2b94a87..18b0546 100644 --- a/include/mocktail/vr/openxr_backend.h +++ b/include/mocktail/vr/openxr_backend.h @@ -305,6 +305,8 @@ class OpenXrBackend final { void HandleSessionState(int state); void HandleReferenceSpaceChange(std::int64_t change_time); bool RecoverSessionLocked(std::uint64_t now_ns); + // False only when the runtime names a GPU other than the guest device's. + bool RuntimeUsesGuestGpuLocked(); void PollSessionEvents(); bool SupportsPresentQueueLocked(VkQueue queue) const; VkQueue GraphicsQueueForPresentLocked(VkQueue present) const; diff --git a/src/vr/openxr_backend.cc b/src/vr/openxr_backend.cc index 5dec0cd..64615ee 100644 --- a/src/vr/openxr_backend.cc +++ b/src/vr/openxr_backend.cc @@ -2059,6 +2059,27 @@ void OpenXrBackend::HandleReferenceSpaceChange(std::int64_t change_time) { } } +bool OpenXrBackend::RuntimeUsesGuestGpuLocked() { + // A runtime that cannot answer for this VkInstance is not proof of a + // mismatch; xrCreateSession then decides. + if (vk_instance_ == VK_NULL_HANDLE) return true; + PFN_xrVoidFunction function = nullptr; + if (XR_FAILED(xrGetInstanceProcAddr(static_cast(xr_instance_), + "xrGetVulkanGraphicsDevice2KHR", + &function)) || + function == nullptr) + return true; + auto info = XrInfo( + XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR); + info.systemId = xr_system_; + info.vulkanInstance = vk_instance_; + VkPhysicalDevice runtime_device = VK_NULL_HANDLE; + if (XR_FAILED(reinterpret_cast(function)( + static_cast(xr_instance_), &info, &runtime_device))) + return true; + return runtime_device == vk_physical_device_; +} + bool OpenXrBackend::RecoverSessionLocked(std::uint64_t now_ns) { if (!session_recovery_pending_ || (graphics_api_ == VrGraphicsApi::kVulkan @@ -2084,6 +2105,15 @@ bool OpenXrBackend::RecoverSessionLocked(std::uint64_t now_ns) { } xr_instance_stale_ = false; Log(" [vr-backend] OpenXR instance recreated for session recovery\n"); + if (graphics_api_ == VrGraphicsApi::kVulkan && + !RuntimeUsesGuestGpuLocked()) { + // The guest device cannot move to another GPU, so no later retry can + // succeed: stop recovering and keep playing without headset output. + session_recovery_pending_ = false; + Log(" [vr-backend] XR OUTPUT UNAVAILABLE: the restarted OpenXR runtime " + "uses a different GPU than Roblox. Restart Nightcap to use VR.\n"); + return false; + } } // nullptr means reuse the verified queue family of this existing VkDevice. if (!(graphics_api_ == VrGraphicsApi::kVulkan