// @lint-ignore-every LICENSELINT // Copyright Epic Games, Inc. All Rights Reserved. #include "OculusXRHMDModule.h" #include "OculusXRHMD.h" #include "OculusXRHMDPrivateRHI.h" #include "OculusXRHMDRuntimeSettings.h" #include "Containers/StringConv.h" #include "Misc/EngineVersion.h" #include "Misc/Paths.h" #if PLATFORM_ANDROID #include "Android/AndroidApplication.h" #include "Android/AndroidPlatformMisc.h" #endif #include "Interfaces/IPluginManager.h" #include "ShaderCore.h" #include "OculusXRTelemetry.h" #if PLATFORM_WINDOWS #include "OculusXRSimulator.h" #include "OculusXRSyntheticEnvironmentServer.h" #endif #if !PLATFORM_ANDROID #if !UE_BUILD_SHIPPING namespace { void __cdecl OvrpLogCallback2(ovrpLogLevel InLevel, const char* Message, int Length) { ELogVerbosity::Type OutLevel; switch (InLevel) { case ovrpLogLevel_Debug: OutLevel = ELogVerbosity::Log; break; case ovrpLogLevel_Info: OutLevel = ELogVerbosity::Display; break; case ovrpLogLevel_Error: OutLevel = ELogVerbosity::Error; break; default: OutLevel = ELogVerbosity::NoLogging; } const FString MessageStr(Length, Message); GLog->CategorizedLogf(TEXT("LogOVRPlugin"), OutLevel, TEXT("%s"), *MessageStr); } } // namespace #endif // !UE_BUILD_SHIPPING #endif // !PLATFORM_ANDROID //------------------------------------------------------------------------------------------------- // FOculusXRHMDModule //------------------------------------------------------------------------------------------------- OculusPluginWrapper FOculusXRHMDModule::PluginWrapper{}; #if OCULUS_HMD_SUPPORTED_PLATFORMS OculusPluginWrapper& FOculusXRHMDModule::GetPluginWrapper() { return PluginWrapper; } #endif // OCULUS_HMD_SUPPORTED_PLATFORMS FOculusXRHMDModule::FOculusXRHMDModule() { #if OCULUS_HMD_SUPPORTED_PLATFORMS bPreInit = false; bPreInitCalled = false; OVRPluginHandle = nullptr; GraphicsAdapterLuid = 0; #endif } void FOculusXRHMDModule::StartupModule() { IHeadMountedDisplayModule::StartupModule(); FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("OculusXR"))->GetBaseDir(), TEXT("Shaders")); AddShaderSourceDirectoryMapping(TEXT("/Plugin/OculusXR"), PluginShaderDir); } void FOculusXRHMDModule::ShutdownModule() { #if OCULUS_HMD_SUPPORTED_PLATFORMS if (PluginWrapper.IsInitialized()) { OculusXRTelemetry::FTelemetryBackend::OnEditorShutdown(); PluginWrapper.Shutdown2(); OculusPluginWrapper::DestroyOculusPluginWrapper(&PluginWrapper); } if (OVRPluginHandle) { FPlatformProcess::FreeDllHandle(OVRPluginHandle); OVRPluginHandle = nullptr; } #endif } #if PLATFORM_ANDROID extern bool AndroidThunkCpp_IsOculusMobileApplication(); #endif FString FOculusXRHMDModule::GetModuleKeyName() const { return FString(TEXT("OculusXRHMD")); } void FOculusXRHMDModule::GetModuleAliases(TArray& AliasesOut) const { // Pre-OculusXR rename (5.0.3 v44) AliasesOut.Add(TEXT("OculusHMD")); } bool FOculusXRHMDModule::PreInit() { #if OCULUS_HMD_SUPPORTED_PLATFORMS if (!bPreInitCalled) { bPreInit = false; #if PLATFORM_ANDROID bPreInitCalled = true; if (!AndroidThunkCpp_IsOculusMobileApplication()) { UE_LOG(LogHMD, Log, TEXT("App is not packaged for Oculus Mobile")); return false; } #endif // Init module if app can render if (FApp::CanEverRender()) { // Load OVRPlugin OVRPluginHandle = GetOVRPluginHandle(); if (!OVRPluginHandle) { UE_LOG(LogHMD, Log, TEXT("Failed loading OVRPlugin %s"), TEXT(OVRP_VERSION_STR)); return false; } if (!OculusPluginWrapper::InitializeOculusPluginWrapper(&PluginWrapper)) { UE_LOG(LogHMD, Log, TEXT("Failed InitializeOculusPluginWrapper")); return false; } // Initialize OVRPlugin ovrpRenderAPIType PreinitApiType = ovrpRenderAPI_None; #if PLATFORM_ANDROID void* Activity = (void*)FAndroidApplication::GetGameActivityThis(); PreinitApiType = ovrpRenderAPI_Vulkan; #else void* Activity = nullptr; #endif #if !PLATFORM_ANDROID #if !UE_BUILD_SHIPPING PluginWrapper.SetLogCallback2(OvrpLogCallback2); #endif // !UE_BUILD_SHIPPING #endif // !PLATFORM_ANDROID if (OVRP_FAILURE(PluginWrapper.PreInitialize5(Activity, PreinitApiType, ovrpPreinitializeFlags::ovrpPreinitializeFlag_None))) { UE_LOG(LogHMD, Log, TEXT("Failed initializing OVRPlugin %s"), TEXT(OVRP_VERSION_STR)); #if WITH_EDITOR && PLATFORM_WINDOWS // In the editor, we want to allow the headset to connect after the editor has booted. // To do this, we must have PreInit() return true, to prevent the HMD module from being unloaded. return GIsEditor; #else return false; #endif } #if PLATFORM_WINDOWS bPreInitCalled = true; const LUID* DisplayAdapterId; if (OVRP_SUCCESS(PluginWrapper.GetDisplayAdapterId2((const void**)&DisplayAdapterId)) && DisplayAdapterId) { SetGraphicsAdapterLuid(*(const uint64*)DisplayAdapterId); } else { UE_LOG(LogHMD, Log, TEXT("Could not determine HMD display adapter")); } const WCHAR* AudioInDeviceId; if (OVRP_SUCCESS(PluginWrapper.GetAudioInDeviceId2((const void**)&AudioInDeviceId)) && AudioInDeviceId) { GConfig->SetString(TEXT("Oculus.Settings"), TEXT("AudioInputDevice"), AudioInDeviceId, GEngineIni); } else { UE_LOG(LogHMD, Log, TEXT("Could not determine HMD audio input device")); } const WCHAR* AudioOutDeviceId; if (OVRP_SUCCESS(PluginWrapper.GetAudioOutDeviceId2((const void**)&AudioOutDeviceId)) && AudioOutDeviceId) { GConfig->SetString(TEXT("Oculus.Settings"), TEXT("AudioOutputDevice"), AudioOutDeviceId, GEngineIni); } else { UE_LOG(LogHMD, Log, TEXT("Could not determine HMD audio output device")); } #endif float ModulePriority; if (!GConfig->GetFloat(TEXT("HMDPluginPriority"), *GetModuleKeyName(), ModulePriority, GEngineIni)) { // if user doesn't set priority set it for them to allow this hmd to be used if enabled ModulePriority = 45.0f; GConfig->SetFloat(TEXT("HMDPluginPriority"), *GetModuleKeyName(), ModulePriority, GEngineIni); } UE_LOG(LogHMD, Log, TEXT("FOculusXRHMDModule PreInit successfully")); bPreInit = true; } } return bPreInit; #else return false; #endif // OCULUS_HMD_SUPPORTED_PLATFORMS } bool FOculusXRHMDModule::IsHMDConnected() { #if OCULUS_HMD_SUPPORTED_PLATFORMS UOculusXRHMDRuntimeSettings* HMDSettings = GetMutableDefault(); if (FApp::CanEverRender() && HMDSettings->XrApi != EOculusXRXrApi::NativeOpenXR) { return true; } #endif // OCULUS_HMD_SUPPORTED_PLATFORMS return false; } uint64 FOculusXRHMDModule::GetGraphicsAdapterLuid() { #if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 || OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 if (!GraphicsAdapterLuid) { int GraphicsAdapter; if (GConfig->GetInt(TEXT("Oculus.Settings"), TEXT("GraphicsAdapter"), GraphicsAdapter, GEngineIni) && GraphicsAdapter >= 0) { TRefCountPtr DXGIFactory; TRefCountPtr DXGIAdapter; DXGI_ADAPTER_DESC DXGIAdapterDesc; if (SUCCEEDED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)DXGIFactory.GetInitReference())) && SUCCEEDED(DXGIFactory->EnumAdapters(GraphicsAdapter, DXGIAdapter.GetInitReference())) && SUCCEEDED(DXGIAdapter->GetDesc(&DXGIAdapterDesc))) { FMemory::Memcpy(&GraphicsAdapterLuid, &DXGIAdapterDesc.AdapterLuid, sizeof(GraphicsAdapterLuid)); } } } #endif #if OCULUS_HMD_SUPPORTED_PLATFORMS return GraphicsAdapterLuid; #else return 0; #endif } FString FOculusXRHMDModule::GetAudioInputDevice() { FString AudioInputDevice; #if OCULUS_HMD_SUPPORTED_PLATFORMS GConfig->GetString(TEXT("Oculus.Settings"), TEXT("AudioInputDevice"), AudioInputDevice, GEngineIni); #endif return AudioInputDevice; } FString FOculusXRHMDModule::GetAudioOutputDevice() { FString AudioOutputDevice; #if OCULUS_HMD_SUPPORTED_PLATFORMS #if PLATFORM_WINDOWS if (bPreInit) { if (FApp::CanEverRender()) { const WCHAR* audioOutDeviceId; if (OVRP_SUCCESS(PluginWrapper.GetAudioOutDeviceId2((const void**)&audioOutDeviceId)) && audioOutDeviceId) { AudioOutputDevice = audioOutDeviceId; } } } #else GConfig->GetString(TEXT("Oculus.Settings"), TEXT("AudioOutputDevice"), AudioOutputDevice, GEngineIni); #endif #endif return AudioOutputDevice; } TSharedPtr FOculusXRHMDModule::CreateTrackingSystem() { #if OCULUS_HMD_SUPPORTED_PLATFORMS if (bPreInit || (GIsEditor && PLATFORM_WINDOWS)) { //If -HMDSimulator is used as the command option to launch UE, use simulator runtime instead of the physical HMD runtime (like PC-Link). if (FParse::Param(FCommandLine::Get(), TEXT("HMDSimulator")) && GetMutableDefault()->MetaXRJsonPath.FilePath.Len()) { if (!IsSimulatorActivated()) { ToggleOpenXRRuntime(); } } OculusXRHMD::FOculusXRHMDPtr OculusXRHMD = FSceneViewExtensions::NewExtension(); if (OculusXRHMD->Startup()) { HeadMountedDisplay = OculusXRHMD; return OculusXRHMD; } } HeadMountedDisplay = nullptr; #endif return nullptr; } TSharedPtr FOculusXRHMDModule::GetVulkanExtensions() { #if OCULUS_HMD_SUPPORTED_PLATFORMS if (bPreInit) { if (!VulkanExtensions.IsValid()) { VulkanExtensions = MakeShareable(new OculusXRHMD::FVulkanExtensions); } } #if WITH_EDITOR && PLATFORM_WINDOWS else if (GIsEditor) { // OpenXR has no ability to query for possible vulkan extensions without connecting a HMD. // This is a problem, because we need to create our VkInstance and VkDevice to render in 2D and there's no HMD. // For now, as a workaround, we hardcode the extensions that Oculus's OpenXR implementation needs. // Eventually, one of three things has to happen for a proper fix: // // 1. OculusXRHMD (or, better, OVRPlugin) maintains a separate VkInstance that has the right extensions, // and uses the vk_external extensions to transfer data between them when needed. // 2. OpenXR changes to allow querying instance and device extensions without an active HMD. // It may still require a physical device handle to list device extensions. // 3. Oculus's Link implementation for OpenXR changes to allow an XrSystemId to be created before a headset // is connected (possibly as an opt-in OpenXR extension for backwards compatibility). // // (2) or (3) are preferable, but if OpenXR is held constant we will have to do (1). if (!VulkanExtensions.IsValid()) { VulkanExtensions = MakeShareable(new OculusXRHMD::FEditorVulkanExtensions); } } #endif return VulkanExtensions; #endif return nullptr; } FString FOculusXRHMDModule::GetDeviceSystemName() { #if OCULUS_HMD_SUPPORTED_PLATFORMS ovrpSystemHeadset SystemHeadset; if (PluginWrapper.IsInitialized() && OVRP_SUCCESS(PluginWrapper.GetSystemHeadsetType2(&SystemHeadset))) { switch (SystemHeadset) { case ovrpSystemHeadset_Oculus_Quest: return FString("Oculus Quest"); case ovrpSystemHeadset_Oculus_Quest_2: default: return FString("Oculus Quest2"); #ifdef WITH_OCULUS_BRANCH case ovrpSystemHeadset_Meta_Quest_Pro: return FString("Meta Quest Pro"); case ovrpSystemHeadset_Meta_Quest_3: return FString("Meta Quest 3"); #endif // WITH_OCULUS_BRANCH } } return FString(); #else return FString(); #endif } bool FOculusXRHMDModule::IsStandaloneStereoOnlyDevice() { #if PLATFORM_ANDROID return FAndroidMisc::GetDeviceMake() == FString("Oculus"); #else return false; #endif } bool FOculusXRHMDModule::IsSimulatorActivated() { #if PLATFORM_WINDOWS return FMetaXRSimulator::IsSimulatorActivated(); #else return false; #endif } void FOculusXRHMDModule::ToggleOpenXRRuntime() { #if PLATFORM_WINDOWS FMetaXRSimulator::ToggleOpenXRRuntime(); #endif } void FOculusXRHMDModule::LaunchEnvironment(FString EnvironmentName) { #if PLATFORM_WINDOWS FMetaXRSES::LaunchEnvironment(EnvironmentName); #endif } void FOculusXRHMDModule::StopServer() { #if PLATFORM_WINDOWS FMetaXRSES::StopServer(); #endif } #if OCULUS_HMD_SUPPORTED_PLATFORMS void* FOculusXRHMDModule::GetOVRPluginHandle() { void* OVRPluginHandle = nullptr; #if PLATFORM_WINDOWS FString XrApi; if (!FModuleManager::Get().IsModuleLoaded("OpenXRHMD") || !GConfig->GetString(TEXT("/Script/OculusXRHMD.OculusXRHMDRuntimeSettings"), TEXT("XrApi"), XrApi, GEngineIni) || XrApi.Equals(FString("OVRPluginOpenXR"))) { FString BinariesPath = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("OculusXR"))->GetBaseDir(), TEXT("/Source/ThirdParty/OVRPlugin/OVRPlugin/Lib/Win64")); FPlatformProcess::PushDllDirectory(*BinariesPath); OVRPluginHandle = FPlatformProcess::GetDllHandle(*(BinariesPath / "OpenXR/OVRPlugin.dll")); FPlatformProcess::PopDllDirectory(*BinariesPath); } #elif PLATFORM_ANDROID OVRPluginHandle = FPlatformProcess::GetDllHandle(TEXT("libOVRPlugin.so")); #endif // PLATFORM_ANDROID return OVRPluginHandle; } bool FOculusXRHMDModule::PoseToOrientationAndPosition(const FQuat& InOrientation, const FVector& InPosition, FQuat& OutOrientation, FVector& OutPosition) const { OculusXRHMD::CheckInGameThread(); OculusXRHMD::FOculusXRHMD* OculusXRHMD = static_cast(HeadMountedDisplay.Pin().Get()); if (OculusXRHMD) { ovrpPosef InPose; InPose.Orientation = OculusXRHMD::ToOvrpQuatf(InOrientation); InPose.Position = OculusXRHMD::ToOvrpVector3f(InPosition); OculusXRHMD::FPose OutPose; if (OculusXRHMD->ConvertPose(InPose, OutPose)) { OutOrientation = OutPose.Orientation; OutPosition = OutPose.Position; return true; } } return false; } void FOculusXRHMDModule::SetGraphicsAdapterLuid(uint64 InLuid) { GraphicsAdapterLuid = InLuid; #if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 || OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 TRefCountPtr DXGIFactory; if (SUCCEEDED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)DXGIFactory.GetInitReference()))) { for (int32 adapterIndex = 0;; adapterIndex++) { TRefCountPtr DXGIAdapter; DXGI_ADAPTER_DESC DXGIAdapterDesc; if (FAILED(DXGIFactory->EnumAdapters(adapterIndex, DXGIAdapter.GetInitReference())) || FAILED(DXGIAdapter->GetDesc(&DXGIAdapterDesc))) { break; } if (!FMemory::Memcmp(&GraphicsAdapterLuid, &DXGIAdapterDesc.AdapterLuid, sizeof(GraphicsAdapterLuid))) { // Remember this adapterIndex so we use the right adapter, even when we startup without HMD connected GConfig->SetInt(TEXT("Oculus.Settings"), TEXT("GraphicsAdapter"), adapterIndex, GEngineIni); break; } } } #endif // OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 || OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 } #endif // OCULUS_HMD_SUPPORTED_PLATFORMS IMPLEMENT_MODULE(FOculusXRHMDModule, OculusXRHMD)