510 lines
14 KiB
C++
510 lines
14 KiB
C++
// @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<FString>& 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<UOculusXRHMDRuntimeSettings>();
|
|
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<IDXGIFactory> DXGIFactory;
|
|
TRefCountPtr<IDXGIAdapter> 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<class IXRTrackingSystem, ESPMode::ThreadSafe> 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<UOculusXRHMDRuntimeSettings>()->MetaXRJsonPath.FilePath.Len())
|
|
{
|
|
if (!IsSimulatorActivated())
|
|
{
|
|
ToggleOpenXRRuntime();
|
|
}
|
|
}
|
|
|
|
OculusXRHMD::FOculusXRHMDPtr OculusXRHMD = FSceneViewExtensions::NewExtension<OculusXRHMD::FOculusXRHMD>();
|
|
|
|
if (OculusXRHMD->Startup())
|
|
{
|
|
HeadMountedDisplay = OculusXRHMD;
|
|
return OculusXRHMD;
|
|
}
|
|
}
|
|
HeadMountedDisplay = nullptr;
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedPtr<IHeadMountedDisplayVulkanExtensions, ESPMode::ThreadSafe> 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<OculusXRHMD::FOculusXRHMD*>(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<IDXGIFactory> DXGIFactory;
|
|
|
|
if (SUCCEEDED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)DXGIFactory.GetInitReference())))
|
|
{
|
|
for (int32 adapterIndex = 0;; adapterIndex++)
|
|
{
|
|
TRefCountPtr<IDXGIAdapter> 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)
|