1314 lines
46 KiB
C++
1314 lines
46 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "OpenXRHMDModule.h"
|
|
#include "Misc/EngineVersion.h"
|
|
#include "Misc/ConfigUtilities.h"
|
|
#include "OpenXRHMD.h"
|
|
#include "OpenXRHMD_RenderBridge.h"
|
|
#include "OpenXRCore.h"
|
|
#include "IOpenXRExtensionPlugin.h"
|
|
#include "IOpenXRARModule.h"
|
|
#include "OpenXRHMDSettings.h"
|
|
#include "BuildSettings.h"
|
|
#include "GeneralProjectSettings.h"
|
|
#include "Epic_openxr.h"
|
|
|
|
#if PLATFORM_ANDROID
|
|
#include <android_native_app_glue.h>
|
|
extern struct android_app* GNativeAndroidApp;
|
|
|
|
extern bool AndroidThunkCpp_IsOculusMobileApplication();
|
|
#endif
|
|
|
|
static TAutoConsoleVariable<int32> CVarEnableOpenXRValidationLayer(
|
|
TEXT("xr.EnableOpenXRValidationLayer"),
|
|
0,
|
|
TEXT("If true, enables the OpenXR validation layer, which will provide extended validation of\nOpenXR API calls. This should only be used for debugging purposes.\n")
|
|
TEXT("Changes will only take effect in new game/editor instances - can't be changed at runtime.\n"),
|
|
ECVF_Default); // @todo: Should we specify ECVF_Cheat here so this doesn't show up in release builds?
|
|
|
|
static TAutoConsoleVariable<bool> CVarDisableOpenXROnAndroidWithoutOculus(
|
|
TEXT("xr.DisableOpenXROnAndroidWithoutOculus"),
|
|
true,
|
|
TEXT("If true OpenXR will not initialize on Android unless the project is packaged for Oculus (ProjectSetting->Platforms->Android->Advanced APK Packaging->PackageForOculusMobileDevices list not empty). Currently defaulted to true because the OpenXR loader we are using hangs during intialization on some devices instead of failing, as it should."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<bool> CVarCheckOpenXRInstanceConformance(
|
|
TEXT("xr.CheckOpenXRInstanceConformance"),
|
|
true,
|
|
TEXT("If true, OpenXR will verify Instance is conformant by calling xrStringToPath. Some runtimes fail without a system attached at instance creation time."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<bool> CVarRetainPreInitInstance(
|
|
TEXT("xr.RetainPreInitInstance"),
|
|
false,
|
|
TEXT("If true, OpenXR will retain any instance created during PreInit rather than destroying it. Destroying it is more correct because we are not yet certain to have chosen OpenXRHMD, and another HMD plugin could take over and try to create an instance of its own which would fail on a runtime that supports only one."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<bool> CVarPreferFBSpaceWarp(
|
|
TEXT("xr.PreferFBSpaceWarp"),
|
|
false,
|
|
TEXT("If true, OpenXR will use XR_FB_space_warp rather than XR_EXT_frame_synthesis to provide frame synthesis when both extensions are available."),
|
|
ECVF_ReadOnly);
|
|
|
|
//---------------------------------------------------
|
|
// OpenXRHMD Plugin Implementation
|
|
//---------------------------------------------------
|
|
|
|
IMPLEMENT_MODULE( FOpenXRHMDModule, OpenXRHMD )
|
|
|
|
FOpenXRHMDModule::FOpenXRHMDModule()
|
|
: LoaderHandle(nullptr)
|
|
, Instance(XR_NULL_HANDLE)
|
|
, InstanceOpenXRAPIVersion(EOpenXRAPIVersion::V_INVALID)
|
|
, RenderBridge(nullptr)
|
|
, OculusAudioInputDevice()
|
|
, OculusAudioOutputDevice()
|
|
{ }
|
|
|
|
FOpenXRHMDModule::~FOpenXRHMDModule()
|
|
{
|
|
}
|
|
|
|
TSharedPtr< class IXRTrackingSystem, ESPMode::ThreadSafe > FOpenXRHMDModule::CreateTrackingSystem()
|
|
{
|
|
if (!InitInstance())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (!RenderBridge && !FParse::Param(FCommandLine::Get(), TEXT("xrtrackingonly")))
|
|
{
|
|
if (!InitRenderBridge())
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
auto ARModule = FModuleManager::LoadModulePtr<IOpenXRARModule>("OpenXRAR");
|
|
auto ARSystem = ARModule->CreateARSystem();
|
|
|
|
if (!Instance)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
auto OpenXRHMD = FSceneViewExtensions::NewExtension<FOpenXRHMD>(Instance, RenderBridge, EnabledExtensions, ExtensionPlugins, ARSystem, InstanceOpenXRAPIVersion);
|
|
if (OpenXRHMD->IsInitialized())
|
|
{
|
|
ARModule->SetTrackingSystem(OpenXRHMD.Get());
|
|
OpenXRHMD->GetARCompositionComponent()->InitializeARSystem();
|
|
return OpenXRHMD;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool FOpenXRHMDModule::PreInit()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
// On Windows, we need to get the audio input/output devices before init, so create the instance first, grab the audio devices, and then
|
|
// immediately destroy it so a new one can be created for the actual initialize call
|
|
const bool bInitialized = InitInstance();
|
|
if (bInitialized)
|
|
{
|
|
if (IsExtensionEnabled(XR_OCULUS_AUDIO_DEVICE_GUID_EXTENSION_NAME))
|
|
{
|
|
PFN_xrGetAudioInputDeviceGuidOculus GetAudioInputDeviceGuidOculus = nullptr;
|
|
if (XR_ENSURE(xrGetInstanceProcAddr(Instance, "xrGetAudioInputDeviceGuidOculus", (PFN_xrVoidFunction*)&GetAudioInputDeviceGuidOculus)))
|
|
{
|
|
WCHAR DeviceGuid[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS];
|
|
GetAudioInputDeviceGuidOculus(Instance, DeviceGuid);
|
|
OculusAudioInputDevice = FString(XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS, DeviceGuid);
|
|
OculusAudioInputDevice.TrimToNullTerminator();
|
|
}
|
|
|
|
PFN_xrGetAudioOutputDeviceGuidOculus GetAudioOutputDeviceGuidOculus = nullptr;
|
|
if (XR_ENSURE(xrGetInstanceProcAddr(Instance, "xrGetAudioOutputDeviceGuidOculus", (PFN_xrVoidFunction*)&GetAudioOutputDeviceGuidOculus)))
|
|
{
|
|
WCHAR DeviceGuid[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS];
|
|
GetAudioOutputDeviceGuidOculus(Instance, DeviceGuid);
|
|
OculusAudioOutputDevice = FString::ConstructFromPtrSize(DeviceGuid, XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS);
|
|
OculusAudioOutputDevice.TrimToNullTerminator();
|
|
}
|
|
}
|
|
if (!CVarRetainPreInitInstance.GetValueOnAnyThread())
|
|
{
|
|
XR_ENSURE(xrDestroyInstance(Instance));
|
|
Instance = nullptr;
|
|
}
|
|
}
|
|
return bInitialized;
|
|
#else
|
|
return true;
|
|
#endif // PLATFORM_WINDOWS
|
|
}
|
|
|
|
void FOpenXRHMDModule::StartupModule()
|
|
{
|
|
IOpenXRHMDModule::StartupModule();
|
|
|
|
// Pull in CVar settings from OpenXRHMDSettings in BaseEngine.ini
|
|
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/OpenXRHMD.OpenXRHMDSettings"), *GEngineIni, ECVF_SetByProjectSetting);
|
|
}
|
|
|
|
void FOpenXRHMDModule::ShutdownModule()
|
|
{
|
|
if (Instance)
|
|
{
|
|
XR_ENSURE(xrDestroyInstance(Instance));
|
|
}
|
|
|
|
if (LoaderHandle)
|
|
{
|
|
FPlatformProcess::FreeDllHandle(LoaderHandle);
|
|
LoaderHandle = nullptr;
|
|
}
|
|
|
|
IOpenXRHMDModule::ShutdownModule();
|
|
}
|
|
|
|
uint64 FOpenXRHMDModule::GetGraphicsAdapterLuid()
|
|
{
|
|
uint64 DefaultValue = 0;
|
|
|
|
// Mac platforms expect the device ID to be returned here
|
|
#if PLATFORM_MAC
|
|
DefaultValue = (uint64)-1;
|
|
#endif
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("xrtrackingonly")))
|
|
{
|
|
return DefaultValue;
|
|
}
|
|
|
|
if (!RenderBridge)
|
|
{
|
|
if (!InitRenderBridge())
|
|
{
|
|
return DefaultValue;
|
|
}
|
|
}
|
|
|
|
FConfigFile* EngineIni = GConfig->FindConfigFile(GEngineIni);
|
|
XrSystemId System = GetSystemId();
|
|
if (!System)
|
|
{
|
|
int64 AdapterLuid = (int64)DefaultValue;
|
|
EngineIni->GetInt64(TEXT("OpenXR.Settings"), TEXT("GraphicsAdapter"), AdapterLuid);
|
|
return reinterpret_cast<uint64&>(AdapterLuid);
|
|
}
|
|
|
|
uint64 AdapterLuid = RenderBridge->GetGraphicsAdapterLuid(System);
|
|
if (AdapterLuid)
|
|
{
|
|
// Remember this luid so we use the right adapter, even when we startup without an HMD connected
|
|
EngineIni->SetInt64(TEXT("OpenXR.Settings"), TEXT("GraphicsAdapter"), reinterpret_cast<int64&>(AdapterLuid));
|
|
}
|
|
return AdapterLuid;
|
|
}
|
|
|
|
TSharedPtr< IHeadMountedDisplayVulkanExtensions, ESPMode::ThreadSafe > FOpenXRHMDModule::GetVulkanExtensions()
|
|
{
|
|
#ifdef XR_USE_GRAPHICS_API_VULKAN
|
|
if (InitInstance() && IsExtensionEnabled(XR_KHR_VULKAN_ENABLE_EXTENSION_NAME))
|
|
{
|
|
XrSystemId System = GetSystemId();
|
|
if (!System)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (!VulkanExtensions.IsValid())
|
|
{
|
|
VulkanExtensions = MakeShareable(new FOpenXRHMD::FVulkanExtensions(Instance, System));
|
|
}
|
|
return VulkanExtensions;
|
|
}
|
|
#endif//XR_USE_GRAPHICS_API_VULKAN
|
|
return nullptr;
|
|
}
|
|
|
|
FString FOpenXRHMDModule::GetDeviceSystemName()
|
|
{
|
|
if (InitInstance())
|
|
{
|
|
XrSystemId System = GetSystemId();
|
|
if (System)
|
|
{
|
|
XrSystemProperties SystemProperties;
|
|
SystemProperties.type = XR_TYPE_SYSTEM_PROPERTIES;
|
|
SystemProperties.next = nullptr;
|
|
XR_ENSURE(xrGetSystemProperties(Instance, System, &SystemProperties));
|
|
|
|
return FString(UTF8_TO_TCHAR(SystemProperties.systemName)); //-V614
|
|
}
|
|
}
|
|
return FString("");
|
|
}
|
|
|
|
bool FOpenXRHMDModule::IsStandaloneStereoOnlyDevice()
|
|
{
|
|
if (InitInstance())
|
|
{
|
|
for (IOpenXRExtensionPlugin* Module : ExtensionPlugins)
|
|
{
|
|
if (Module->IsStandaloneStereoOnlyDevice())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#if PLATFORM_ANDROID
|
|
return IStereoRendering::IsStartInVR();
|
|
#endif
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FOpenXRHMDModule::EnumerateExtensions()
|
|
{
|
|
uint32_t ExtensionsCount = 0;
|
|
if (XR_FAILED(xrEnumerateInstanceExtensionProperties(nullptr, 0, &ExtensionsCount, nullptr)))
|
|
{
|
|
// If it fails this early that means there's no runtime installed
|
|
UE_LOG(LogHMD, Log, TEXT("xrEnumerateInstanceExtensionProperties failed, suggests no runtime is installed."));
|
|
return false;
|
|
}
|
|
|
|
TArray<XrExtensionProperties> Properties;
|
|
Properties.SetNum(ExtensionsCount);
|
|
for (auto& Prop : Properties)
|
|
{
|
|
Prop = XrExtensionProperties{ XR_TYPE_EXTENSION_PROPERTIES };
|
|
}
|
|
|
|
if (XR_ENSURE(xrEnumerateInstanceExtensionProperties(nullptr, ExtensionsCount, &ExtensionsCount, Properties.GetData())))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("OpenXR runtime supported extensions:"));
|
|
for (const XrExtensionProperties& Prop : Properties)
|
|
{
|
|
AvailableExtensions.Add(Prop.extensionName);
|
|
UE_LOG(LogHMD, Log, TEXT("\t%S"), (Prop.extensionName));
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FOpenXRHMDModule::EnumerateLayers()
|
|
{
|
|
uint32 LayerPropertyCount = 0;
|
|
if (XR_FAILED(xrEnumerateApiLayerProperties(0, &LayerPropertyCount, nullptr)))
|
|
{
|
|
// As per EnumerateExtensions - a failure here means no runtime installed.
|
|
return false;
|
|
}
|
|
|
|
if (!LayerPropertyCount)
|
|
{
|
|
// It's still legit if we have no layers, so early out here (and return success) if so.
|
|
return true;
|
|
}
|
|
|
|
TArray<XrApiLayerProperties> LayerProperties;
|
|
LayerProperties.SetNum(LayerPropertyCount);
|
|
for (auto& Prop : LayerProperties)
|
|
{
|
|
Prop = XrApiLayerProperties{ XR_TYPE_API_LAYER_PROPERTIES };
|
|
}
|
|
|
|
if (XR_ENSURE(xrEnumerateApiLayerProperties(LayerPropertyCount, &LayerPropertyCount, LayerProperties.GetData())))
|
|
{
|
|
for (const auto& Prop : LayerProperties)
|
|
{
|
|
AvailableLayers.Add(Prop.layerName);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct AnsiKeyFunc : BaseKeyFuncs<const ANSICHAR*, const ANSICHAR*, false>
|
|
{
|
|
typedef typename TTypeTraits<const ANSICHAR*>::ConstPointerType KeyInitType;
|
|
typedef typename TCallTraits<const ANSICHAR*>::ParamType ElementInitType;
|
|
|
|
/**
|
|
* @return The key used to index the given element.
|
|
*/
|
|
static FORCEINLINE KeyInitType GetSetKey(ElementInitType Element)
|
|
{
|
|
return Element;
|
|
}
|
|
|
|
/**
|
|
* @return True if the keys match.
|
|
*/
|
|
static FORCEINLINE bool Matches(KeyInitType A, KeyInitType B)
|
|
{
|
|
return FCStringAnsi::Strcmp(A, B) == 0;
|
|
}
|
|
|
|
/** Calculates a hash index for a key. */
|
|
static FORCEINLINE uint32 GetKeyHash(KeyInitType Key)
|
|
{
|
|
return GetTypeHash(Key);
|
|
}
|
|
};
|
|
|
|
bool FOpenXRHMDModule::InitRenderBridge()
|
|
{
|
|
// Get all extension plugins
|
|
TSet<const ANSICHAR*, AnsiKeyFunc> ExtensionSet;
|
|
TArray<IOpenXRExtensionPlugin*> ExtModules = IModularFeatures::Get().GetModularFeatureImplementations<IOpenXRExtensionPlugin>(IOpenXRExtensionPlugin::GetModularFeatureName());
|
|
|
|
// Query all extension plugins to see if we need to use a custom render bridge
|
|
PFN_xrGetInstanceProcAddr GetProcAddr = nullptr;
|
|
for (IOpenXRExtensionPlugin* Plugin : ExtModules)
|
|
{
|
|
// We are taking ownership of the CustomRenderBridge instance here.
|
|
TRefCountPtr<FOpenXRRenderBridge> CustomRenderBridge = Plugin->GetCustomRenderBridge(Instance);
|
|
if (CustomRenderBridge)
|
|
{
|
|
// We pick the first
|
|
RenderBridge = CustomRenderBridge;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (GDynamicRHI == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!InitInstance())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const ERHIInterfaceType RHIType = RHIGetInterfaceType();
|
|
|
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
|
if (RHIType == ERHIInterfaceType::D3D11 && IsExtensionEnabled(XR_KHR_D3D11_ENABLE_EXTENSION_NAME))
|
|
{
|
|
RenderBridge = CreateRenderBridge_D3D11(Instance);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef XR_USE_GRAPHICS_API_D3D12
|
|
if (RHIType == ERHIInterfaceType::D3D12 && IsExtensionEnabled(XR_KHR_D3D12_ENABLE_EXTENSION_NAME))
|
|
{
|
|
RenderBridge = CreateRenderBridge_D3D12(Instance);
|
|
}
|
|
else
|
|
#endif
|
|
#if defined(XR_USE_GRAPHICS_API_OPENGL_ES) && defined(XR_USE_PLATFORM_ANDROID)
|
|
if (RHIType == ERHIInterfaceType::OpenGL && IsExtensionEnabled(XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME))
|
|
{
|
|
RenderBridge = CreateRenderBridge_OpenGLES(Instance);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef XR_USE_GRAPHICS_API_OPENGL
|
|
if (RHIType == ERHIInterfaceType::OpenGL && IsExtensionEnabled(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME))
|
|
{
|
|
RenderBridge = CreateRenderBridge_OpenGL(Instance);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef XR_USE_GRAPHICS_API_VULKAN
|
|
if (RHIType == ERHIInterfaceType::Vulkan && IsExtensionEnabled(XR_KHR_VULKAN_ENABLE_EXTENSION_NAME))
|
|
{
|
|
RenderBridge = CreateRenderBridge_Vulkan(Instance);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
FString RHIString = FApp::GetGraphicsRHI();
|
|
UE_LOG(LogHMD, Warning, TEXT("%s is not currently supported by the OpenXR runtime"), *RHIString);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
PFN_xrGetInstanceProcAddr FOpenXRHMDModule::GetDefaultLoader()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
#if PLATFORM_64BITS
|
|
#if PLATFORM_CPU_ARM_FAMILY
|
|
FString BinariesPath = FPaths::EngineDir() / FString(TEXT("Binaries/ThirdParty/OpenXR/WinArm64"));
|
|
#else
|
|
FString BinariesPath = FPaths::EngineDir() / FString(TEXT("Binaries/ThirdParty/OpenXR/win64"));
|
|
#endif
|
|
#else
|
|
FString BinariesPath = FPaths::EngineDir() / FString(TEXT("Binaries/ThirdParty/OpenXR/win32"));
|
|
#endif
|
|
|
|
FString LoaderName = "openxr_loader.dll";
|
|
FPlatformProcess::PushDllDirectory(*BinariesPath);
|
|
LoaderHandle = FPlatformProcess::GetDllHandle(*LoaderName);
|
|
FPlatformProcess::PopDllDirectory(*BinariesPath);
|
|
#elif PLATFORM_LINUX
|
|
|
|
#if PLATFORM_LINUXARM64
|
|
// We do not currently have a binary for linuxarm64, so we will fail to find a default loader.
|
|
// It is possible to work around this by using the CustomLoader functionality to specify a loader.
|
|
// When we get one we will need to activate the following code, and add the new binary in OpenXR.Build.cs
|
|
//FString BinariesPath = FPaths::EngineDir() / FString(TEXT("Binaries/ThirdParty/OpenXR/linuxarm64/aarch64-unknown-linux-gnueabi"));
|
|
//FString LoaderName = "libopenxr_loader.so";
|
|
//LoaderHandle = FPlatformProcess::GetDllHandle(*(BinariesPath / LoaderName));
|
|
#else
|
|
FString BinariesPath = FPaths::EngineDir() / FString(TEXT("Binaries/ThirdParty/OpenXR/linux/x86_64-unknown-linux-gnu"));
|
|
FString LoaderName = "libopenxr_loader.so";
|
|
LoaderHandle = FPlatformProcess::GetDllHandle(*(BinariesPath / LoaderName));
|
|
#endif
|
|
|
|
#elif PLATFORM_ANDROID
|
|
FString LoaderName = "libopenxr_loader.so";
|
|
LoaderHandle = FPlatformProcess::GetDllHandle(*LoaderName);
|
|
#endif
|
|
|
|
if (!LoaderHandle)
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Failed to find OpenXR runtime loader."));
|
|
return nullptr;
|
|
}
|
|
|
|
PFN_xrGetInstanceProcAddr OutGetProcAddr = (PFN_xrGetInstanceProcAddr)FPlatformProcess::GetDllExport(LoaderHandle, TEXT("xrGetInstanceProcAddr"));
|
|
|
|
#if PLATFORM_ANDROID
|
|
PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
|
|
OutGetProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
|
|
if (xrInitializeLoaderKHR == nullptr)
|
|
{
|
|
UE_LOG(LogHMD, Error, TEXT("Unable to load OpenXR Android xrInitializeLoaderKHR"));
|
|
return nullptr;
|
|
}
|
|
|
|
XrLoaderInitInfoAndroidKHR LoaderInitializeInfoAndroid;
|
|
LoaderInitializeInfoAndroid.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
|
|
LoaderInitializeInfoAndroid.next = NULL;
|
|
LoaderInitializeInfoAndroid.applicationVM = GNativeAndroidApp->activity->vm;
|
|
LoaderInitializeInfoAndroid.applicationContext = GNativeAndroidApp->activity->clazz;
|
|
XR_ENSURE(xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&LoaderInitializeInfoAndroid));
|
|
#endif
|
|
return OutGetProcAddr;
|
|
}
|
|
|
|
bool FOpenXRHMDModule::EnableExtensions(const TArray<const ANSICHAR*>& RequiredExtensions, const TArray<const ANSICHAR*>& OptionalExtensions, TArray<const ANSICHAR*>& OutExtensions)
|
|
{
|
|
// Query required extensions and check if they're all available
|
|
bool ExtensionMissing = false;
|
|
for (const ANSICHAR* Ext : RequiredExtensions)
|
|
{
|
|
if (AvailableExtensions.Contains(Ext))
|
|
{
|
|
UE_LOG(LogHMD, Verbose, TEXT("Required extension %S enabled"), Ext);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogHMD, Warning, TEXT("Required extension %S is not available"), Ext);
|
|
ExtensionMissing = true;
|
|
}
|
|
}
|
|
|
|
// If any required extensions are missing then we ignore the plugin
|
|
if (ExtensionMissing)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// All required extensions are supported we can safely add them to our set and give the plugin callbacks
|
|
OutExtensions.Append(RequiredExtensions);
|
|
|
|
// Add all supported optional extensions to the set
|
|
for (const ANSICHAR* Ext : OptionalExtensions)
|
|
{
|
|
if (AvailableExtensions.Contains(Ext))
|
|
{
|
|
UE_LOG(LogHMD, Verbose, TEXT("Optional extension %S enabled"), Ext);
|
|
OutExtensions.Add(Ext);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Optional extension %S is not available"), Ext);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FOpenXRHMDModule::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
|
|
{
|
|
#if PLATFORM_ANDROID
|
|
OutExtensions.Add(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME);
|
|
#endif
|
|
|
|
// If the commandline -xrtrackingonly is passed, then start the application in _Other mode instead of _Scene mode
|
|
// This is used when we only want to get tracking information and don't need to render anything to the XR device
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("xrtrackingonly")))
|
|
{
|
|
OutExtensions.Add(XR_MND_HEADLESS_EXTENSION_NAME);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FOpenXRHMDModule::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
|
|
{
|
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
|
OutExtensions.Add(XR_KHR_D3D11_ENABLE_EXTENSION_NAME);
|
|
#endif
|
|
#ifdef XR_USE_GRAPHICS_API_D3D12
|
|
OutExtensions.Add(XR_KHR_D3D12_ENABLE_EXTENSION_NAME);
|
|
#endif
|
|
#ifdef XR_USE_GRAPHICS_API_OPENGL
|
|
OutExtensions.Add(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME);
|
|
#endif
|
|
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
|
|
OutExtensions.Add(XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME);
|
|
#endif
|
|
#ifdef XR_USE_GRAPHICS_API_VULKAN
|
|
OutExtensions.Add(XR_KHR_VULKAN_ENABLE_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_KHR_VULKAN_SWAPCHAIN_FORMAT_LIST_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_FB_FOVEATION_VULKAN_EXTENSION_NAME);
|
|
#endif
|
|
OutExtensions.Add(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_FB_COMPOSITION_LAYER_DEPTH_TEST_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_KHR_COMPOSITION_LAYER_EQUIRECT_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_KHR_COMPOSITION_LAYER_EQUIRECT2_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_KHR_COMPOSITION_LAYER_COLOR_SCALE_BIAS_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_KHR_VISIBILITY_MASK_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_EPIC_VIEW_CONFIGURATION_FOV_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_EXT_DPAD_BINDING_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_EXT_PALM_POSE_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_EXT_ACTIVE_ACTION_SET_PRIORITY_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_FB_FOVEATION_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME);
|
|
#if PLATFORM_WINDOWS
|
|
OutExtensions.Add(XR_OCULUS_AUDIO_DEVICE_GUID_EXTENSION_NAME);
|
|
#endif
|
|
|
|
// These extensions are mutually incompatible because XR_FB_composition_layer_alpha_blend disables non-opaque environment blend modes
|
|
// XR_EXT_composition_layer_inverted_alpha is the more modern and platform-agnostic option and is preferred when available
|
|
if (AvailableExtensions.Contains(XR_EXT_COMPOSITION_LAYER_INVERTED_ALPHA_EXTENSION_NAME))
|
|
{
|
|
OutExtensions.Add(XR_EXT_COMPOSITION_LAYER_INVERTED_ALPHA_EXTENSION_NAME);
|
|
}
|
|
else if (AvailableExtensions.Contains(XR_FB_COMPOSITION_LAYER_ALPHA_BLEND_EXTENSION_NAME))
|
|
{
|
|
OutExtensions.Add(XR_FB_COMPOSITION_LAYER_ALPHA_BLEND_EXTENSION_NAME);
|
|
}
|
|
|
|
// These extensions both perform frame synthesis, and require providing motion vector and motion vector depth swapchains at a requested size
|
|
// XR_EXT_frame_synthesis is the more modern and platform-agnostic option and is preferred when available
|
|
// However, it is not currently working properly on Oculus runtimes, so XR_FB_space_warp is preferred on Quest devices
|
|
const bool bSpaceWarpSupported = AvailableExtensions.Contains(XR_FB_SPACE_WARP_EXTENSION_NAME);
|
|
const bool bFrameSynthesisSupported = AvailableExtensions.Contains(XR_EXT_FRAME_SYNTHESIS_EXTENSION_NAME);
|
|
|
|
if (bSpaceWarpSupported && bFrameSynthesisSupported)
|
|
{
|
|
if (CVarPreferFBSpaceWarp.GetValueOnAnyThread())
|
|
{
|
|
OutExtensions.Add(XR_FB_SPACE_WARP_EXTENSION_NAME);
|
|
}
|
|
else
|
|
{
|
|
OutExtensions.Add(XR_EXT_FRAME_SYNTHESIS_EXTENSION_NAME);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bFrameSynthesisSupported)
|
|
{
|
|
OutExtensions.Add(XR_EXT_FRAME_SYNTHESIS_EXTENSION_NAME);
|
|
}
|
|
else if (bSpaceWarpSupported)
|
|
{
|
|
OutExtensions.Add(XR_FB_SPACE_WARP_EXTENSION_NAME);
|
|
}
|
|
}
|
|
|
|
OutExtensions.Add(XR_EXT_LOCAL_FLOOR_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_EXT_USER_PRESENCE_EXTENSION_NAME);
|
|
return true;
|
|
}
|
|
|
|
bool FOpenXRHMDModule::InitInstance()
|
|
{
|
|
if (Instance)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#if PLATFORM_ANDROID
|
|
if (AndroidThunkCpp_IsOculusMobileApplication())
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("OpenXRHMDModule: App is packaged for Oculus Mobile OpenXR"));
|
|
}
|
|
else
|
|
{
|
|
if (CVarDisableOpenXROnAndroidWithoutOculus.GetValueOnAnyThread())
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("OpenXRHMDModule: vr.DisableOpenXROnAndroidWithoutOculus is true and this project is not packaged for Oculus Mobile Devices. Disabling OpenXR."));
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("OpenXRHMDModule: App is packaged for Android OpenXR"));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Get all extension plugins
|
|
TSet<const ANSICHAR*, AnsiKeyFunc> ExtensionSet;
|
|
TArray<IOpenXRExtensionPlugin*> ExtModules = IModularFeatures::Get().GetModularFeatureImplementations<IOpenXRExtensionPlugin>(IOpenXRExtensionPlugin::GetModularFeatureName());
|
|
|
|
// Query all extension plugins to see if we need to use a custom loader
|
|
PFN_xrGetInstanceProcAddr GetProcAddr = nullptr;
|
|
for (IOpenXRExtensionPlugin* Plugin : ExtModules)
|
|
{
|
|
if (Plugin->GetCustomLoader(&GetProcAddr))
|
|
{
|
|
// We pick the first loader we can find
|
|
UE_LOG(LogHMD, Log, TEXT("OpenXRHMDModule::InitInstance found and will use CustomLoader from plugin %s"), *Plugin->GetDisplayName());
|
|
break;
|
|
}
|
|
|
|
// Clear it again just to ensure the failed call didn't leave the pointer set
|
|
GetProcAddr = nullptr;
|
|
}
|
|
|
|
if (!GetProcAddr)
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("OpenXRHMDModule::InitInstance using DefaultLoader."));
|
|
GetProcAddr = GetDefaultLoader();
|
|
}
|
|
|
|
for (IOpenXRExtensionPlugin* Plugin : ExtModules)
|
|
{
|
|
if (Plugin->InsertOpenXRAPILayer(GetProcAddr))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("IOpenXRExtensionPlugin API layer enabled: %s"), *Plugin->GetDisplayName());
|
|
}
|
|
}
|
|
|
|
if (!PreInitOpenXRCore(GetProcAddr))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Failed to initialize core functions. Please check that you have a valid OpenXR runtime installed."));
|
|
return false;
|
|
}
|
|
|
|
if (!EnumerateExtensions())
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Failed to enumerate extensions. Please check that you have a valid OpenXR runtime installed."));
|
|
return false;
|
|
}
|
|
|
|
if (!EnumerateLayers())
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Failed to enumerate API layers. Please check that you have a valid OpenXR runtime installed."));
|
|
return false;
|
|
}
|
|
|
|
// Enable any required and optional extensions that are not plugin specific (usually platform support extensions)
|
|
{
|
|
TArray<const ANSICHAR*> RequiredExtensions, OptionalExtensions, Extensions;
|
|
// Query required extensions
|
|
RequiredExtensions.Empty();
|
|
if (!GetRequiredExtensions(RequiredExtensions))
|
|
{
|
|
UE_LOG(LogHMD, Error, TEXT("Could not get required OpenXR extensions."));
|
|
return false;
|
|
}
|
|
|
|
// Query optional extensions
|
|
OptionalExtensions.Empty();
|
|
if (!GetOptionalExtensions(OptionalExtensions))
|
|
{
|
|
UE_LOG(LogHMD, Error, TEXT("Could not get optional OpenXR extensions."));
|
|
return false;
|
|
}
|
|
|
|
if (!EnableExtensions(RequiredExtensions, OptionalExtensions, Extensions))
|
|
{
|
|
UE_LOG(LogHMD, Error, TEXT("Could not enable all required OpenXR extensions."));
|
|
return false;
|
|
}
|
|
ExtensionSet.Append(Extensions);
|
|
}
|
|
|
|
if (AvailableExtensions.Contains(XR_EPIC_VIEW_CONFIGURATION_FOV_EXTENSION_NAME))
|
|
{
|
|
ExtensionSet.Add(XR_EPIC_VIEW_CONFIGURATION_FOV_EXTENSION_NAME);
|
|
}
|
|
|
|
ExtensionPlugins.Reset();
|
|
for (IOpenXRExtensionPlugin* Plugin : ExtModules)
|
|
{
|
|
TArray<const ANSICHAR*> RequiredExtensions, OptionalExtensions, Extensions;
|
|
|
|
// Query required extensions
|
|
RequiredExtensions.Empty();
|
|
if (!Plugin->GetRequiredExtensions(RequiredExtensions))
|
|
{
|
|
// Ignore the plugin if the query fails
|
|
continue;
|
|
}
|
|
|
|
// Query optional extensions
|
|
OptionalExtensions.Empty();
|
|
if (!Plugin->GetOptionalExtensions(OptionalExtensions))
|
|
{
|
|
// Ignore the plugin if the query fails
|
|
continue;
|
|
}
|
|
|
|
if (!EnableExtensions(RequiredExtensions, OptionalExtensions, Extensions))
|
|
{
|
|
// Ignore the plugin if the required extension could not be enabled
|
|
FString ModuleName = Plugin->GetDisplayName();
|
|
UE_LOG(LogHMD, Log, TEXT("Could not enable all required OpenXR extensions for %s on current system. This plugin will be loaded but ignored, but will be enabled on a target platform that supports the required extension."), *ModuleName);
|
|
continue;
|
|
}
|
|
ExtensionSet.Append(Extensions);
|
|
ExtensionPlugins.Add(Plugin);
|
|
}
|
|
|
|
if (auto ARModule = FModuleManager::LoadModulePtr<IOpenXRARModule>("OpenXRAR"))
|
|
{
|
|
TArray<const ANSICHAR*> ARExtensionSet;
|
|
ARModule->GetExtensions(ARExtensionSet);
|
|
ExtensionSet.Append(ARExtensionSet);
|
|
}
|
|
|
|
EnabledExtensions.Reset();
|
|
for (const ANSICHAR* Ext : ExtensionSet)
|
|
{
|
|
EnabledExtensions.Add(Ext);
|
|
}
|
|
|
|
// Enable layers, if specified by CVar.
|
|
// Note: For the validation layer to work on Windows (as of latest OpenXR runtime, August 2019), the following are required:
|
|
// 1. Download and build the OpenXR SDK from https://github.com/KhronosGroup/OpenXR-SDK-Source (follow instructions at https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/main/BUILDING.md)
|
|
// 2. Add a registry key under HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1\ApiLayers\Explicit, containing the path to the manifest file
|
|
// (e.g. C:\OpenXR-SDK-Source-main\build\win64\src\api_layers\XrApiLayer_core_validation.json) <-- this file is downloaded as part of the SDK source, above
|
|
// 3. Copy the DLL from the build target at, for example, C:\OpenXR-SDK-Source-main\build\win64\src\api_layers\XrApiLayer_core_validation.dll to
|
|
// somewhere in your system path (e.g. c:\windows\system32); the OpenXR loader currently doesn't use the path the json file is in (this is a bug)
|
|
|
|
const bool bEnableOpenXRValidationLayer = (CVarEnableOpenXRValidationLayer.GetValueOnAnyThread() != 0)
|
|
|| FParse::Param(FCommandLine::Get(), TEXT("openxrdebug"))
|
|
|| FParse::Param(FCommandLine::Get(), TEXT("openxrvalidation"));
|
|
TArray<const char*> Layers;
|
|
if (bEnableOpenXRValidationLayer)
|
|
{
|
|
if (AvailableLayers.Contains("XR_APILAYER_LUNARG_core_validation"))
|
|
{
|
|
UE_LOG(LogHMD, Display, TEXT("Running with OpenXR validation layers, performance might be degraded."));
|
|
Layers.Add("XR_APILAYER_LUNARG_core_validation");
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogHMD, Error, TEXT("OpenXR validation was requested, but the validation layer isn't available. Request ignored."));
|
|
}
|
|
}
|
|
|
|
// Engine registration can be disabled via console var.
|
|
auto* CVarDisableEngineAndAppRegistration = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DisableEngineAndAppRegistration"));
|
|
bool bDisableEngineRegistration = (CVarDisableEngineAndAppRegistration && CVarDisableEngineAndAppRegistration->GetValueOnAnyThread() != 0);
|
|
|
|
// EngineName will be of the form "UnrealEngine4.21", with the minor version ("21" in this example)
|
|
// updated with every quarterly release
|
|
FString EngineName = bDisableEngineRegistration ? FString("") : FApp::GetEpicProductIdentifier() + FEngineVersion::Current().ToString(EVersionComponent::Minor);
|
|
FString AppName = bDisableEngineRegistration ? FString("") : FApp::GetProjectName();
|
|
|
|
XrInstanceCreateInfo Info;
|
|
Info.type = XR_TYPE_INSTANCE_CREATE_INFO;
|
|
Info.next = nullptr;
|
|
Info.createFlags = 0;
|
|
FPlatformString::Convert((UTF8CHAR*)Info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE, *AppName, AppName.Len() + 1);
|
|
Info.applicationInfo.applicationVersion = static_cast<uint32>(BuildSettings::GetCurrentChangelist()) | (BuildSettings::IsLicenseeVersion() ? 0x80000000 : 0);
|
|
FPlatformString::Convert((UTF8CHAR*)Info.applicationInfo.engineName, XR_MAX_ENGINE_NAME_SIZE, *EngineName, EngineName.Len() + 1);
|
|
Info.applicationInfo.engineVersion = (uint32)(FEngineVersion::Current().GetMinor() << 16 | FEngineVersion::Current().GetPatch());
|
|
|
|
|
|
Info.applicationInfo.apiVersion = 0; // We will overwrite this setting in TryCreateInstance and potentially try multiple versions
|
|
Info.enabledApiLayerCount = Layers.Num();
|
|
Info.enabledApiLayerNames = Layers.GetData();
|
|
|
|
Info.enabledExtensionCount = EnabledExtensions.Num();
|
|
Info.enabledExtensionNames = EnabledExtensions.GetData();
|
|
|
|
#if PLATFORM_ANDROID
|
|
XrInstanceCreateInfoAndroidKHR InstanceCreateInfoAndroid;
|
|
InstanceCreateInfoAndroid.type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR;
|
|
InstanceCreateInfoAndroid.next = nullptr;
|
|
InstanceCreateInfoAndroid.applicationVM = GNativeAndroidApp->activity->vm;
|
|
InstanceCreateInfoAndroid.applicationActivity = GNativeAndroidApp->activity->clazz;
|
|
Info.next = &InstanceCreateInfoAndroid;
|
|
#endif // PLATFORM_ANDROID
|
|
|
|
if (!TryCreateInstance(Info))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!InitOpenXRCore(Instance))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Failed to initialize core functions. Please check that you have a valid OpenXR runtime installed."));
|
|
return false;
|
|
}
|
|
|
|
XrInstanceProperties InstanceProps = { XR_TYPE_INSTANCE_PROPERTIES, nullptr };
|
|
XR_ENSURE(xrGetInstanceProperties(Instance, &InstanceProps));
|
|
InstanceProps.runtimeName[XR_MAX_RUNTIME_NAME_SIZE - 1] = 0; // Ensure the name is null terminated.
|
|
UE_LOG(LogHMD, Log, TEXT("Initialized OpenXR on %S runtime version %d.%d.%d"), InstanceProps.runtimeName, XR_VERSION_MAJOR(InstanceProps.runtimeVersion), XR_VERSION_MINOR(InstanceProps.runtimeVersion), XR_VERSION_PATCH(InstanceProps.runtimeVersion));
|
|
|
|
if (CVarCheckOpenXRInstanceConformance.GetValueOnAnyThread() &&
|
|
(FCStringAnsi::Strstr(InstanceProps.runtimeName, "SteamVR/OpenXR") != nullptr))
|
|
{
|
|
// Runtimes should not be dependent on system availability to use instance-only functions.
|
|
// However, some runtimes fail with some instance-only calls, such as xrStringToPath. We
|
|
// need to bail early to prevent failures later on during setup.
|
|
XrPath UserHeadTestPath = XR_NULL_PATH;
|
|
const XrResult StringToPathTest = xrStringToPath(Instance, "/user/head", &UserHeadTestPath);
|
|
|
|
if (StringToPathTest != XR_SUCCESS)
|
|
{
|
|
UE_LOG(LogHMD, Warning, TEXT("Instance does not support expected usage of xrStringToPath. Instance is not viable."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (IOpenXRExtensionPlugin* Module : ExtensionPlugins)
|
|
{
|
|
Module->PostCreateInstance(Instance);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct FProblematicOpenXRApiLayerInfo
|
|
{
|
|
FProblematicOpenXRApiLayerInfo(const FString& String)
|
|
{
|
|
FString StringCopy = String;
|
|
StringCopy.RemoveSpacesInline();
|
|
FParse::Value(*String, TEXT("Name="), Name);
|
|
FParse::Value(*String, TEXT("MinVersion="), MinVersion);
|
|
FParse::Value(*String, TEXT("MaxVersion="), MaxVersion);
|
|
FParse::Value(*String, TEXT("ExtensionAddedToFallbackWithout="), ExtensionAddedToFallbackWithout);
|
|
}
|
|
FString Name;
|
|
int MinVersion = 0;
|
|
int MaxVersion = 0; // 0 for both = all.
|
|
FString ExtensionAddedToFallbackWithout; // If this extension is available and instance creation fails with XR_ERROR_EXTENSION_NOT_PRESENT we will try again without it.
|
|
};
|
|
|
|
bool FOpenXRHMDModule::TryCreateInstance(XrInstanceCreateInfo& Info)
|
|
{
|
|
// Cache a copy of the info not modified by ExtensionPlugins, some of which we might have to disable.
|
|
XrInstanceCreateInfo LocalInfo = Info;
|
|
|
|
for (IOpenXRExtensionPlugin* Module : ExtensionPlugins)
|
|
{
|
|
Info.next = Module->OnCreateInstance(this, Info.next);
|
|
}
|
|
|
|
// Try each api version that is enabled last to first.
|
|
bool bIsOpenXR1_1Enabled = true;
|
|
bool bIsOpenXR1_0Enabled = true;
|
|
{
|
|
// Read the ini. This code can run so early that we cannot use a default object to load this more easily.
|
|
// Note the settings are true by default, so if we don't find a setting we use true.
|
|
bool bFoundValue = false;
|
|
if (GConfig->GetBool(TEXT("/Script/OpenXRHMD.OpenXRHMDSettings"), TEXT("bIsOpenXR1_1Enabled"), bFoundValue, GGameIni))
|
|
{
|
|
bIsOpenXR1_1Enabled = bFoundValue;
|
|
}
|
|
if (GConfig->GetBool(TEXT("/Script/OpenXRHMD.OpenXRHMDSettings"), TEXT("bIsOpenXR1_0Enabled"), bFoundValue, GGameIni))
|
|
{
|
|
bIsOpenXR1_0Enabled = bFoundValue;
|
|
}
|
|
}
|
|
|
|
XrResult Result = XR_ERROR_API_VERSION_UNSUPPORTED;
|
|
if (bIsOpenXR1_1Enabled)
|
|
{
|
|
Info.applicationInfo.apiVersion = XR_API_VERSION_1_1;
|
|
Result = xrCreateInstance(&Info, &Instance);
|
|
if (XR_SUCCEEDED(Result))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("xrCreateInstance succeeded with XR_API_VERSION_1_1"));
|
|
InstanceOpenXRAPIVersion = EOpenXRAPIVersion::V_1_1;
|
|
}
|
|
}
|
|
|
|
if (Result == XR_ERROR_API_VERSION_UNSUPPORTED && bIsOpenXR1_0Enabled)
|
|
{
|
|
Info.applicationInfo.apiVersion = XR_API_VERSION_1_0;
|
|
Result = xrCreateInstance(&Info, &Instance);
|
|
if (XR_SUCCEEDED(Result))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("xrCreateInstance succeeded with XR_API_VERSION_1_0"));
|
|
InstanceOpenXRAPIVersion = EOpenXRAPIVersion::V_1_0;
|
|
}
|
|
}
|
|
|
|
// Attempt to deal with problematic extensions
|
|
if (Result == XR_ERROR_EXTENSION_NOT_PRESENT)
|
|
{
|
|
// An extension we requested is not supported, but we normally only add extensions that must be supported and extensions that were listed to us by the runtime, so how did we get into this situation?
|
|
// A badly behaving layer might add an extension into the available extensions list, but then not remove it before the runtime sees that list causing the runtime to fail instance creation with XR_ERROR_EXTENSION_NOT_PRESENT.
|
|
// Therefore we have a ProblematicOpenXRApiLayerInfos config setting to define layers that might give us bad extensions which we can then try to create an instance without.
|
|
|
|
// Read the ini. This code can run so early that we cannot use a default object to load this more easily.
|
|
TArray<FProblematicOpenXRApiLayerInfo> ProblematicOpenXRApiLayerInfos;
|
|
{
|
|
TArray<FString> ProblematicOpenXRApiLayerInfoStrings;
|
|
GConfig->GetArray(TEXT("OpenXR"), TEXT("ProblematicOpenXRApiLayerInfos"), ProblematicOpenXRApiLayerInfoStrings, GEngineIni);
|
|
ProblematicOpenXRApiLayerInfos.Reserve(ProblematicOpenXRApiLayerInfoStrings.Num());
|
|
for (FString& Str : ProblematicOpenXRApiLayerInfoStrings)
|
|
{
|
|
ProblematicOpenXRApiLayerInfos.Emplace(Str);
|
|
}
|
|
}
|
|
|
|
if (ProblematicOpenXRApiLayerInfos.Num() > 0)
|
|
{
|
|
// Copy the currently active ProblematicLayerInfos that have ExtensionsAddedToFallbackWithout into a new array.
|
|
TArray<const FProblematicOpenXRApiLayerInfo*> ActiveProblematicLayerInfos;
|
|
{
|
|
TArray<XrApiLayerProperties> ActiveLayerProperties;
|
|
EnumerateOpenXRApiLayers(ActiveLayerProperties);
|
|
TMap<FString, int> ActiveLayerPropertyNameMap;
|
|
for (int i = 0; i < ActiveLayerProperties.Num(); ++i)
|
|
{
|
|
ActiveLayerPropertyNameMap.Add(ActiveLayerProperties[i].layerName, ActiveLayerProperties[i].layerVersion);
|
|
}
|
|
for (const FProblematicOpenXRApiLayerInfo& ProblematicLayerInfo : ProblematicOpenXRApiLayerInfos)
|
|
{
|
|
// Skip if no extension specified.
|
|
if (ProblematicLayerInfo.ExtensionAddedToFallbackWithout.Len() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
// Check if this one is active.
|
|
int* Found = ActiveLayerPropertyNameMap.Find(ProblematicLayerInfo.Name);
|
|
if (Found)
|
|
{
|
|
if ((ProblematicLayerInfo.MinVersion == 0 && ProblematicLayerInfo.MaxVersion == 0) || (*Found >= ProblematicLayerInfo.MinVersion && *Found <= ProblematicLayerInfo.MaxVersion))
|
|
{
|
|
ActiveProblematicLayerInfos.Add(&ProblematicLayerInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ActiveProblematicLayerInfos.Num() > 0)
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Failed to create an OpenXR instance with all of the extensions that are enabled. The OpenXR runtime says it does not support all of those. "
|
|
"We have a list of Problematic OpenXR ApiLayers that may be adding extensions that are not supported by the runtime. "
|
|
"We can try disabling those extensions to see if we can find a set that the runtime will create an instance for. "
|
|
"Sadly this is a combinatorial process."));
|
|
if (UE_LOG_ACTIVE(LogHMD, Log))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Problematic Layer Extension Information for currently active OpenXRApiLayers:"));
|
|
for (const FProblematicOpenXRApiLayerInfo* LayerInfo : ActiveProblematicLayerInfos)
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT(" Layer: %s Version: %i-%i Extension: %s"), *LayerInfo->Name, LayerInfo->MinVersion, LayerInfo->MaxVersion, *LayerInfo->ExtensionAddedToFallbackWithout);
|
|
}
|
|
}
|
|
|
|
// Build a list of all of the problematic extensions, as ansi char pointers from the EnabledExtensions list.
|
|
TArray<const char*> ProblematicExtensionList;
|
|
{
|
|
TSet<const FString*> ProblematicExtensionSet;
|
|
for (const FProblematicOpenXRApiLayerInfo* LayerInfo : ActiveProblematicLayerInfos)
|
|
{
|
|
ProblematicExtensionSet.Add(&(LayerInfo->ExtensionAddedToFallbackWithout));
|
|
}
|
|
ProblematicExtensionList.Reserve(ProblematicExtensionSet.Num());
|
|
for (const FString* ProblematicExtension : ProblematicExtensionSet)
|
|
{
|
|
auto ProblematicExtensionConverter = StringCast<ANSICHAR>(**ProblematicExtension);
|
|
const char* ProblematicExtensionCStr = ProblematicExtensionConverter.Get();
|
|
for (uint32_t i = 0; i < Info.enabledExtensionCount; ++i)
|
|
{
|
|
const char* EnabledExtension = Info.enabledExtensionNames[i];
|
|
if (FCStringAnsi::Strncmp(ProblematicExtensionCStr, EnabledExtension, XR_MAX_EXTENSION_NAME_SIZE) == 0)
|
|
{
|
|
ProblematicExtensionList.Add(EnabledExtension);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a list of enabled extensions excluding the problematic ones.
|
|
TArray<const char*> LocalEnabledExtensions;
|
|
LocalEnabledExtensions.Reserve(Info.enabledExtensionCount);
|
|
for (uint32_t i = 0; i < Info.enabledExtensionCount; i++)
|
|
{
|
|
const char* EnabledExtension = Info.enabledExtensionNames[i];
|
|
if (!ProblematicExtensionList.Contains(EnabledExtension))
|
|
{
|
|
LocalEnabledExtensions.Add(EnabledExtension);
|
|
}
|
|
}
|
|
|
|
// Try to xrCreateInstance with each combination until one succeeds or all fail
|
|
|
|
// So now we have a list of non-problematic extensions (LocalEnabledExtensions) and a list of problematic ones (ProblematicExtensionList).
|
|
// We want to try enabling the problematic extensions, exploring all combinations, and last we will try enabling none of them.
|
|
// This whole algorithm is N^2, but N is actually always 1 right now. We will just clamp N to 3 and assert. If we actually want to use it with more
|
|
// It'll need some kind of rewrite.
|
|
const int StartIndex = LocalEnabledExtensions.Num();
|
|
int32 NumProblematicExtensions = ProblematicExtensionList.Num();
|
|
check(NumProblematicExtensions < 64);
|
|
check(NumProblematicExtensions <= 3);
|
|
NumProblematicExtensions = FMath::Max(NumProblematicExtensions, 3);
|
|
uint64 Bitfield = (1ull << (NumProblematicExtensions)) - 1; // Get a 1 for each extension.
|
|
do
|
|
{
|
|
--Bitfield; // We do this first because we already tried with all enabled.
|
|
|
|
// Enable some of the problematic extensions
|
|
LocalEnabledExtensions.SetNum(StartIndex, EAllowShrinking::No); // Shrink off problematic ones from previous iterations
|
|
for (int i = 0; i < NumProblematicExtensions; ++i)
|
|
{
|
|
if (((Bitfield >> i) & 1) == 1)
|
|
{
|
|
LocalEnabledExtensions.Add(ProblematicExtensionList[i]);
|
|
}
|
|
}
|
|
|
|
// Fill in the Instance creation info struct
|
|
LocalInfo.enabledExtensionCount = LocalEnabledExtensions.Num();
|
|
LocalInfo.enabledExtensionNames = LocalEnabledExtensions.GetData();
|
|
|
|
// Figure out which extensionplugins need to be disabled because their required extensions are disabled.
|
|
TArray<IOpenXRExtensionPlugin*> LocalExtensionPlugins = ExtensionPlugins;
|
|
TArray<IOpenXRExtensionPlugin*> LocallyDisabledExtensionPlugins;
|
|
for (int i = LocalExtensionPlugins.Num() - 1; i >= 0 ; --i)
|
|
{
|
|
IOpenXRExtensionPlugin* ExtensionPlugin = LocalExtensionPlugins[i];
|
|
|
|
TArray<const ANSICHAR*> RequiredExtensions;
|
|
if (!ExtensionPlugin->GetRequiredExtensions(RequiredExtensions))
|
|
{
|
|
UE_LOG(LogHMD, Error, TEXT("Could not get required OpenXR extensions."));
|
|
return false;
|
|
}
|
|
bool bAllRequiredAreEnabled = true;
|
|
for (const ANSICHAR* Required : RequiredExtensions)
|
|
{
|
|
if (!LocalEnabledExtensions.Contains(Required))
|
|
{
|
|
bAllRequiredAreEnabled = false;
|
|
LocallyDisabledExtensionPlugins.Add(ExtensionPlugin);
|
|
break;
|
|
}
|
|
}
|
|
if (!bAllRequiredAreEnabled)
|
|
{
|
|
LocalExtensionPlugins.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
// Log what we are trying now.
|
|
if (UE_LOG_ACTIVE(LogHMD, Log))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Attempting to create OpenXR instance with some extensions disabled. The following extensions were enabled:"), OpenXRResultToString(Result));
|
|
for (const char* Extension : EnabledExtensions)
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("- %S"), Extension);
|
|
}
|
|
UE_LOG(LogHMD, Log, TEXT("The following Extensions were disabled because of the ProblematicOpenXRApiLayerInfos:"));
|
|
for (int i = 0; i < NumProblematicExtensions; ++i)
|
|
{
|
|
if (((Bitfield >> i) & 1) == 0)
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("- %hs"), ProblematicExtensionList[i]);
|
|
}
|
|
}
|
|
UE_LOG(LogHMD, Log, TEXT("The following OpenXRExtensionPlugins were disabled because we disabled some or all of their required extensions:"));
|
|
for (IOpenXRExtensionPlugin* DisabledPlugin : LocallyDisabledExtensionPlugins)
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("- %s"), *DisabledPlugin->GetDisplayName());
|
|
}
|
|
}
|
|
|
|
// Create!
|
|
for (IOpenXRExtensionPlugin* Module : LocalExtensionPlugins)
|
|
{
|
|
LocalInfo.next = Module->OnCreateInstance(this, LocalInfo.next);
|
|
}
|
|
XrResult Result2 = xrCreateInstance(&LocalInfo, &Instance);
|
|
if (XR_SUCCEEDED(Result2))
|
|
{
|
|
if (UE_LOG_ACTIVE(LogHMD, Log))
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Successfully created OpenXR Instance with the following Extensions disabled because of the ProblematicOpenXRApiLayerInfos:"));
|
|
for (int i = 0; i < NumProblematicExtensions; ++i)
|
|
{
|
|
if (((Bitfield >> i) & 1) == 0)
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("- %s"), StringCast<TCHAR>(ProblematicExtensionList[i]).Get());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update External data, because we changed it.
|
|
Info = LocalInfo;
|
|
EnabledExtensions = LocalEnabledExtensions;
|
|
ExtensionPlugins = LocalExtensionPlugins;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogHMD, Log, TEXT("Failed to create an OpenXR instance with some extensions disabled. Result is %s."), OpenXRResultToString(Result));
|
|
}
|
|
} while (Bitfield > 0);
|
|
|
|
UE_LOG(LogHMD, Log, TEXT("We did not find a combination of extensions that works using the ProblematicOpenXRApiLayerInfos. Instance creation always failed."));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (XR_FAILED(Result))
|
|
{
|
|
UE_LOG(LogHMD, Warning, TEXT("Failed to create an OpenXR instance, result is %s. Please check if you have an OpenXR runtime installed."), OpenXRResultToString(Result));
|
|
UE_LOG(LogHMD, Warning, TEXT("The following OpenXR API versions were enabled (see project settings:plugins:OpenXR):"));
|
|
if (bIsOpenXR1_1Enabled)
|
|
{
|
|
UE_LOG(LogHMD, Warning, TEXT("- 1.1"));
|
|
}
|
|
if (bIsOpenXR1_0Enabled)
|
|
{
|
|
UE_LOG(LogHMD, Warning, TEXT("- 1.0"));
|
|
}
|
|
UE_LOG(LogHMD, Warning, TEXT("The following extensions were enabled:"));
|
|
for (const char* Extension : EnabledExtensions)
|
|
{
|
|
UE_LOG(LogHMD, Warning, TEXT("- %S"), Extension);
|
|
}
|
|
UE_LOG(LogHMD, Warning, TEXT("The following layers were enumerated:"));
|
|
TArray<XrApiLayerProperties> EnumeratedLayers;
|
|
EnumerateOpenXRApiLayers(EnumeratedLayers);
|
|
for (XrApiLayerProperties& Layer : EnumeratedLayers)
|
|
{
|
|
UE_LOG(LogHMD, Warning, TEXT("- %S"), Layer.layerName);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
UE_LOG(LogHMD, Log, TEXT("xrCreateInstance created: %llu"), Instance);
|
|
|
|
return true;
|
|
}
|
|
|
|
XrSystemId FOpenXRHMDModule::GetSystemId() const
|
|
{
|
|
XrSystemId System = XR_NULL_SYSTEM_ID;
|
|
|
|
XrSystemGetInfo SystemInfo;
|
|
SystemInfo.type = XR_TYPE_SYSTEM_GET_INFO;
|
|
SystemInfo.next = nullptr;
|
|
SystemInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
|
for (IOpenXRExtensionPlugin* Module : ExtensionPlugins)
|
|
{
|
|
SystemInfo.next = Module->OnGetSystem(Instance, SystemInfo.next);
|
|
}
|
|
|
|
XrResult Result = xrGetSystem(Instance, &SystemInfo, &System);
|
|
if (XR_FAILED(Result))
|
|
{
|
|
UE_LOG(LogHMD, VeryVerbose, TEXT("Failed to get an OpenXR system, result is %s"), OpenXRResultToString(Result));
|
|
return XR_NULL_SYSTEM_ID;
|
|
}
|
|
|
|
for (IOpenXRExtensionPlugin* Module : ExtensionPlugins)
|
|
{
|
|
Module->PostGetSystem(Instance, System);
|
|
}
|
|
|
|
return System;
|
|
}
|
|
|
|
FName FOpenXRHMDModule::ResolvePathToName(XrPath Path)
|
|
{
|
|
{
|
|
FReadScopeLock Lock(NameMutex);
|
|
FName* FoundName = PathToName.Find(Path);
|
|
if (FoundName)
|
|
{
|
|
// We've already previously resolved this XrPath to an FName
|
|
return *FoundName;
|
|
}
|
|
}
|
|
|
|
uint32 PathCount = 0;
|
|
char PathChars[XR_MAX_PATH_LENGTH];
|
|
XrResult Result = xrPathToString(Instance, Path, XR_MAX_PATH_LENGTH, &PathCount, PathChars);
|
|
check(XR_SUCCEEDED(Result));
|
|
if (Result == XR_SUCCESS)
|
|
{
|
|
// Resolve this XrPath to an FName and store it in the name map
|
|
FName Name(PathCount - 1, PathChars);
|
|
|
|
FWriteScopeLock Lock(NameMutex);
|
|
PathToName.Add(Path, Name);
|
|
NameToPath.Add(Name, Path);
|
|
return Name;
|
|
}
|
|
else
|
|
{
|
|
return NAME_None;
|
|
}
|
|
}
|
|
|
|
XrPath FOpenXRHMDModule::ResolveNameToPath(FName Name)
|
|
{
|
|
{
|
|
FReadScopeLock Lock(NameMutex);
|
|
XrPath* FoundPath = NameToPath.Find(Name);
|
|
if (FoundPath)
|
|
{
|
|
// We've already previously resolved this FName to an XrPath
|
|
return *FoundPath;
|
|
}
|
|
}
|
|
|
|
XrPath Path = XR_NULL_PATH;
|
|
FString PathString = Name.ToString();
|
|
XrResult Result = xrStringToPath(Instance, StringCast<ANSICHAR>(*PathString).Get(), &Path);
|
|
check(XR_SUCCEEDED(Result));
|
|
if (Result == XR_SUCCESS)
|
|
{
|
|
FWriteScopeLock Lock(NameMutex);
|
|
PathToName.Add(Path, Name);
|
|
NameToPath.Add(Name, Path);
|
|
return Path;
|
|
}
|
|
else
|
|
{
|
|
return XR_NULL_PATH;
|
|
}
|
|
}
|
|
|
|
FString FOpenXRHMDModule::GetAudioInputDevice()
|
|
{
|
|
return OculusAudioInputDevice;
|
|
}
|
|
|
|
FString FOpenXRHMDModule::GetAudioOutputDevice()
|
|
{
|
|
return OculusAudioOutputDevice;
|
|
}
|