VR2024_WizardVR/TheProject/VRWizardProject/Plugins/MetaXR/Source/OculusXRHMD/Private/OculusXRHMD.cpp

4877 lines
166 KiB
C++

// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRHMD.h"
#include "OculusXRHMDPrivateRHI.h"
#include "EngineAnalytics.h"
#include "Interfaces/IAnalyticsProvider.h"
#include "AnalyticsEventAttribute.h"
#include "Slate/SceneViewport.h"
#include "PostProcess/PostProcessHMD.h"
#include "PostProcess/SceneRenderTargets.h"
#include "HardwareInfo.h"
#include "ScreenRendering.h"
#include "GameFramework/PlayerController.h"
#include "Math/UnrealMathUtility.h"
#include "Math/TranslationMatrix.h"
#include "Widgets/SViewport.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/SlateApplication.h"
#include "Engine/Canvas.h"
#include "Engine/GameEngine.h"
#include "Engine/RendererSettings.h"
#include "Misc/CoreDelegates.h"
#include "GameFramework/WorldSettings.h"
#include "Engine/StaticMesh.h"
#include "Engine/StaticMeshActor.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Misc/EngineVersion.h"
#include "ClearQuad.h"
#include "DynamicResolutionState.h"
#include "DynamicResolutionProxy.h"
#include "OculusXRHMDRuntimeSettings.h"
#include "OculusXRDelegates.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "GenericPlatform/GenericPlatformMath.h"
#include "LegacyScreenPercentageDriver.h"
#if PLATFORM_ANDROID
#include "Android/AndroidJNI.h"
#include "Android/AndroidApplication.h"
#include "HAL/IConsoleManager.h"
#include "AndroidPermissionFunctionLibrary.h"
#include "AndroidPermissionCallbackProxy.h"
#endif
#include "OculusShaders.h"
#include "PipelineStateCache.h"
#include "IOculusXRMRModule.h"
#if WITH_EDITOR
#include "Editor/UnrealEd/Classes/Editor/EditorEngine.h"
#include "Settings/LevelEditorPlaySettings.h"
#endif
#if !UE_BUILD_SHIPPING
#include "Debug/DebugDrawService.h"
#endif
#if OCULUS_HMD_SUPPORTED_PLATFORMS
static TAutoConsoleVariable<int32> CVarOculusEnableSubsampledLayout(
TEXT("r.Mobile.Oculus.EnableSubsampled"),
0,
TEXT("0: Disable subsampled layout (Default)\n")
TEXT("1: Enable subsampled layout on supported platforms\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarOculusEnableLowLatencyVRS(
TEXT("r.Mobile.Oculus.EnableLowLatencyVRS"),
0,
TEXT("0: Disable late update of VRS textures (Default)\n")
TEXT("1: Enable late update of VRS textures\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarOculusForceSymmetric(
TEXT("r.Mobile.Oculus.ForceSymmetric"),
0,
TEXT("0: Use standard runtime-provided projection matrices (Default)\n")
TEXT("1: Render both eyes with a symmetric projection, union of both FOVs (and corresponding higher rendertarget size to maintain PD)\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
// AppSpaceWarp
static TAutoConsoleVariable<int32> CVarOculusEnableSpaceWarpUser(
TEXT("r.Mobile.Oculus.SpaceWarp.Enable"),
0,
TEXT("0 Disable spacewarp at runtime.\n")
TEXT("1 Enable spacewarp at runtime.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarOculusEnableSpaceWarpInternal(
TEXT("r.Mobile.Oculus.SpaceWarp.EnableInternal"),
0,
TEXT("0 Disable spacewarp, for internal engine checking, don't modify.\n")
TEXT("1 Enable spacewarp, for internal enegine checking, don't modify.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
// Foveated Rendering
static TAutoConsoleVariable<int32> CVarOculusFoveatedRenderingMethod(
TEXT("r.Mobile.Oculus.FoveatedRendering.Method"),
-1,
TEXT("0 Fixed Foveated Rendering.\n")
TEXT("1 Eye-Tracked Foveated Rendering.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarOculusFoveatedRenderingLevel(
TEXT("r.Mobile.Oculus.FoveatedRendering.Level"),
-1,
TEXT("0 Off.\n")
TEXT("1 Low.\n")
TEXT("2 Medium.\n")
TEXT("3 High.\n")
TEXT("4 High Top.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarOculusDynamicFoveatedRendering(
TEXT("r.Mobile.Oculus.FoveatedRendering.Dynamic"),
-1,
TEXT("0 Disable Dynamic Foveated Rendering at runtime.\n")
TEXT("1 Enable Dynamic Foveated Rendering at runtime.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarOculusDynamicResolutionPixelDensity(
TEXT("r.Oculus.DynamicResolution.PixelDensity"),
0,
TEXT("0 Static Pixel Density corresponding to Pixel Density 1.0 (default)\n")
TEXT(">0 Manual Pixel Density Override\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
#define OCULUS_PAUSED_IDLE_FPS 10
static const FString USE_SCENE_PERMISSION_NAME("com.oculus.permission.USE_SCENE");
namespace OculusXRHMD
{
#if !UE_BUILD_SHIPPING
static void __cdecl OvrpLogCallback(ovrpLogLevel level, const char* message)
{
FString tbuf = ANSI_TO_TCHAR(message);
const TCHAR* levelStr = TEXT("");
switch (level)
{
case ovrpLogLevel_Debug:
levelStr = TEXT(" Debug:");
break;
case ovrpLogLevel_Info:
levelStr = TEXT(" Info:");
break;
case ovrpLogLevel_Error:
levelStr = TEXT(" Error:");
break;
}
GLog->Logf(TEXT("OCULUS:%s %s"), levelStr, *tbuf);
}
#endif // !UE_BUILD_SHIPPING
//-------------------------------------------------------------------------------------------------
// FOculusXRHMD
//-------------------------------------------------------------------------------------------------
const FName FOculusXRHMD::OculusSystemName(TEXT("OculusXRHMD"));
FName FOculusXRHMD::GetSystemName() const
{
return OculusSystemName;
}
int32 FOculusXRHMD::GetXRSystemFlags() const
{
return EXRSystemFlags::IsHeadMounted;
}
FString FOculusXRHMD::GetVersionString() const
{
const char* Version;
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetVersion2(&Version)))
{
Version = "Unknown";
}
return FString::Printf(TEXT("OVRPlugin: %s"), UTF8_TO_TCHAR(Version));
}
bool FOculusXRHMD::DoesSupportPositionalTracking() const
{
ovrpBool trackingPositionSupported;
return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetTrackingPositionSupported2(&trackingPositionSupported)) && trackingPositionSupported;
}
bool FOculusXRHMD::HasValidTrackingPosition()
{
ovrpBool nodePositionTracked;
return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePositionTracked2(ovrpNode_Head, &nodePositionTracked)) && nodePositionTracked;
}
struct TrackedDevice
{
ovrpNode Node;
EXRTrackedDeviceType Type;
};
static TrackedDevice TrackedDevices[] = {
{ ovrpNode_Head, EXRTrackedDeviceType::HeadMountedDisplay },
{ ovrpNode_HandLeft, EXRTrackedDeviceType::Controller },
{ ovrpNode_HandRight, EXRTrackedDeviceType::Controller },
{ ovrpNode_TrackerZero, EXRTrackedDeviceType::TrackingReference },
{ ovrpNode_TrackerOne, EXRTrackedDeviceType::TrackingReference },
{ ovrpNode_TrackerTwo, EXRTrackedDeviceType::TrackingReference },
{ ovrpNode_TrackerThree, EXRTrackedDeviceType::TrackingReference },
{ ovrpNode_DeviceObjectZero, EXRTrackedDeviceType::Other },
};
static uint32 TrackedDeviceCount = sizeof(TrackedDevices) / sizeof(TrackedDevices[0]);
bool FOculusXRHMD::EnumerateTrackedDevices(TArray<int32>& OutDevices, EXRTrackedDeviceType Type)
{
CheckInGameThread();
for (uint32 TrackedDeviceId = 0; TrackedDeviceId < TrackedDeviceCount; TrackedDeviceId++)
{
if (Type == EXRTrackedDeviceType::Any || Type == TrackedDevices[TrackedDeviceId].Type)
{
ovrpBool nodePresent;
ovrpNode Node = TrackedDevices[TrackedDeviceId].Node;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePresent2(Node, &nodePresent)) && nodePresent)
{
const int32 ExternalDeviceId = OculusXRHMD::ToExternalDeviceId(Node);
OutDevices.Add(ExternalDeviceId);
}
}
}
return true;
}
void FOculusXRHMD::UpdateRTPoses()
{
CheckInRenderThread();
FGameFrame* CurrentFrame = GetFrame_RenderThread();
if (CurrentFrame)
{
if (!CurrentFrame->Flags.bRTLateUpdateDone)
{
FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, CurrentFrame->FrameNumber, 0.0);
CurrentFrame->Flags.bRTLateUpdateDone = true;
}
}
// else, Frame_RenderThread has already been reset/rendered (or not created yet).
// This can happen when DoEnableStereo() is called, as SetViewportSize (which it calls) enques a render
// immediately - meaning two render frames were enqueued in the span of one game tick.
}
bool FOculusXRHMD::GetCurrentPose(int32 InDeviceId, FQuat& OutOrientation, FVector& OutPosition)
{
OutOrientation = FQuat::Identity;
OutPosition = FVector::ZeroVector;
if ((size_t)InDeviceId >= TrackedDeviceCount)
{
return false;
}
ovrpNode Node = OculusXRHMD::ToOvrpNode(InDeviceId);
const FSettings* CurrentSettings;
FGameFrame* CurrentFrame;
if (InRenderThread())
{
CurrentSettings = GetSettings_RenderThread();
CurrentFrame = GetFrame_RenderThread();
UpdateRTPoses();
}
else if (InGameThread())
{
CurrentSettings = GetSettings();
CurrentFrame = NextFrameToRender.Get();
}
else
{
return false;
}
if (!CurrentSettings || !CurrentFrame)
{
return false;
}
ovrpPoseStatef PoseState;
FPose Pose;
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame->FrameNumber, Node, &PoseState)) || !ConvertPose_Internal(PoseState.Pose, Pose, CurrentSettings, CurrentFrame->WorldToMetersScale))
{
return false;
}
OutPosition = Pose.Position;
OutOrientation = Pose.Orientation;
return true;
}
bool FOculusXRHMD::GetRelativeEyePose(int32 InDeviceId, int32 ViewIndex, FQuat& OutOrientation, FVector& OutPosition)
{
OutOrientation = FQuat::Identity;
OutPosition = FVector::ZeroVector;
if (InDeviceId != HMDDeviceId)
{
return false;
}
ovrpNode Node;
switch (ViewIndex)
{
case EStereoscopicEye::eSSE_LEFT_EYE:
Node = ovrpNode_EyeLeft;
break;
case EStereoscopicEye::eSSE_RIGHT_EYE:
Node = ovrpNode_EyeRight;
break;
case EStereoscopicEye::eSSE_MONOSCOPIC:
Node = ovrpNode_EyeCenter;
break;
default:
return false;
}
const FSettings* CurrentSettings;
FGameFrame* CurrentFrame;
if (InRenderThread())
{
CurrentSettings = GetSettings_RenderThread();
CurrentFrame = GetFrame_RenderThread();
UpdateRTPoses();
}
else if (InGameThread())
{
CurrentSettings = GetSettings();
CurrentFrame = NextFrameToRender.Get();
}
else
{
return false;
}
if (!CurrentSettings || !CurrentFrame)
{
return false;
}
ovrpPoseStatef HmdPoseState, EyePoseState;
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame->FrameNumber, ovrpNode_Head, &HmdPoseState)) || OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame->FrameNumber, Node, &EyePoseState)))
{
return false;
}
FPose HmdPose, EyePose;
HmdPose.Orientation = ToFQuat(HmdPoseState.Pose.Orientation);
HmdPose.Position = ToFVector(HmdPoseState.Pose.Position) * CurrentFrame->WorldToMetersScale;
EyePose.Orientation = ToFQuat(EyePoseState.Pose.Orientation);
EyePose.Position = ToFVector(EyePoseState.Pose.Position) * CurrentFrame->WorldToMetersScale;
FQuat HmdOrientationInv = HmdPose.Orientation.Inverse();
OutOrientation = HmdOrientationInv * EyePose.Orientation;
OutOrientation.Normalize();
OutPosition = HmdOrientationInv.RotateVector(EyePose.Position - HmdPose.Position);
return true;
}
bool FOculusXRHMD::GetTrackingSensorProperties(int32 InDeviceId, FQuat& OutOrientation, FVector& OutPosition, FXRSensorProperties& OutSensorProperties)
{
CheckInGameThread();
if ((size_t)InDeviceId >= TrackedDeviceCount)
{
return false;
}
ovrpNode Node = OculusXRHMD::ToOvrpNode(InDeviceId);
ovrpPoseStatef PoseState;
FPose Pose;
ovrpFrustum2f Frustum;
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, OVRP_CURRENT_FRAMEINDEX, Node, &PoseState)) || !ConvertPose(PoseState.Pose, Pose) || OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodeFrustum2(Node, &Frustum)))
{
return false;
}
OutPosition = Pose.Position;
OutOrientation = Pose.Orientation;
OutSensorProperties.LeftFOV = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.LeftTan));
OutSensorProperties.RightFOV = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.RightTan));
OutSensorProperties.TopFOV = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.UpTan));
OutSensorProperties.BottomFOV = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.DownTan));
OutSensorProperties.NearPlane = Frustum.zNear * Frame->WorldToMetersScale;
OutSensorProperties.FarPlane = Frustum.zFar * Frame->WorldToMetersScale;
OutSensorProperties.CameraDistance = 1.0f * Frame->WorldToMetersScale;
return true;
}
void FOculusXRHMD::SetTrackingOrigin(EHMDTrackingOrigin::Type InOrigin)
{
TrackingOrigin = InOrigin;
ovrpTrackingOrigin ovrpOrigin = ovrpTrackingOrigin_EyeLevel;
if (InOrigin == EHMDTrackingOrigin::Floor)
ovrpOrigin = ovrpTrackingOrigin_FloorLevel;
if (InOrigin == EHMDTrackingOrigin::Stage)
ovrpOrigin = ovrpTrackingOrigin_Stage;
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
EHMDTrackingOrigin::Type lastOrigin = GetTrackingOrigin();
FOculusXRHMDModule::GetPluginWrapper().SetTrackingOriginType2(ovrpOrigin);
OCFlags.NeedSetTrackingOrigin = false;
if (lastOrigin != InOrigin)
Settings->BaseOffset = FVector::ZeroVector;
}
OnTrackingOriginChanged();
}
EHMDTrackingOrigin::Type FOculusXRHMD::GetTrackingOrigin() const
{
EHMDTrackingOrigin::Type rv = EHMDTrackingOrigin::Eye;
ovrpTrackingOrigin ovrpOrigin = ovrpTrackingOrigin_EyeLevel;
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetTrackingOriginType2(&ovrpOrigin)))
{
switch (ovrpOrigin)
{
case ovrpTrackingOrigin_EyeLevel:
rv = EHMDTrackingOrigin::Eye;
break;
case ovrpTrackingOrigin_FloorLevel:
rv = EHMDTrackingOrigin::Floor;
break;
case ovrpTrackingOrigin_Stage:
rv = EHMDTrackingOrigin::Stage;
break;
default:
UE_LOG(LogHMD, Error, TEXT("Unsupported ovr tracking origin type %d"), int(ovrpOrigin));
break;
}
}
return rv;
}
bool FOculusXRHMD::GetFloorToEyeTrackingTransform(FTransform& OutFloorToEye) const
{
float EyeHeight = 0.f;
const bool bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserEyeHeight2(&EyeHeight));
OutFloorToEye = FTransform(FVector(0.f, 0.f, -ConvertFloat_M2U(EyeHeight)));
return bSuccess;
}
void FOculusXRHMD::ResetOrientationAndPosition(float yaw)
{
Recenter(RecenterOrientationAndPosition, yaw);
}
void FOculusXRHMD::ResetOrientation(float yaw)
{
Recenter(RecenterOrientation, yaw);
}
void FOculusXRHMD::ResetPosition()
{
Recenter(RecenterPosition, 0);
}
void FOculusXRHMD::Recenter(FRecenterTypes RecenterType, float Yaw)
{
CheckInGameThread();
if (NextFrameToRender)
{
const bool floorLevel = GetTrackingOrigin() != EHMDTrackingOrigin::Eye;
ovrpPoseStatef poseState;
FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, NextFrameToRender->FrameNumber, 0.0);
FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, NextFrameToRender->FrameNumber, ovrpNode_Head, &poseState);
if (RecenterType & RecenterPosition)
{
Settings->BaseOffset = ToFVector(poseState.Pose.Position);
if (floorLevel)
Settings->BaseOffset.Z = 0;
}
if (RecenterType & RecenterOrientation)
{
Settings->BaseOrientation = FRotator(0, FRotator(ToFQuat(poseState.Pose.Orientation)).Yaw - Yaw, 0).Quaternion();
}
}
}
void FOculusXRHMD::SetBaseRotation(const FRotator& BaseRot)
{
SetBaseOrientation(BaseRot.Quaternion());
}
FRotator FOculusXRHMD::GetBaseRotation() const
{
return GetBaseOrientation().Rotator();
}
void FOculusXRHMD::SetBaseOrientation(const FQuat& BaseOrient)
{
CheckInGameThread();
Settings->BaseOrientation = BaseOrient;
}
FQuat FOculusXRHMD::GetBaseOrientation() const
{
CheckInGameThread();
return Settings->BaseOrientation;
}
bool FOculusXRHMD::IsHeadTrackingEnforced() const
{
if (IsInGameThread())
{
return Settings.IsValid() && Settings->Flags.bHeadTrackingEnforced;
}
else
{
CheckInRenderThread();
return Settings_RenderThread.IsValid() && Settings_RenderThread->Flags.bHeadTrackingEnforced;
}
}
void FOculusXRHMD::SetHeadTrackingEnforced(bool bEnabled)
{
CheckInGameThread();
check(Settings.IsValid());
const bool bOldValue = Settings->Flags.bHeadTrackingEnforced;
Settings->Flags.bHeadTrackingEnforced = bEnabled;
if (!bEnabled)
{
ResetControlRotation();
}
else if (!bOldValue)
{
InitDevice();
}
}
bool FOculusXRHMD::IsHeadTrackingAllowed() const
{
bool bNeedEnableStereo = false;
CheckInGameThread();
if (!FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
return false;
}
#if PLATFORM_WINDOWS
// TODO: This is a temp fix of the case that callers wants to use IsHeadTrackingAllowed() to do something in UGameEngine::Start().
// Settings->Flags.bStereoEnabled won't be true until Window.IsValid() and UGameEngine::Tick() starts which is very late.
// We might need a better mechanism to decouple Window.IsValid() and Settings->Flags.bStereoEnabled.
bNeedEnableStereo = !GIsEditor && Flags.bNeedEnableStereo;
#endif
return (FHeadMountedDisplayBase::IsHeadTrackingAllowed() || bNeedEnableStereo);
}
void FOculusXRHMD::OnBeginPlay(FWorldContext& InWorldContext)
{
CheckInGameThread();
CachedViewportWidget.Reset();
CachedWindow.Reset();
bHardOcclusionsEnabled = false;
#if WITH_EDITOR
// @TODO: add more values here.
// This call make sense when 'Play' is used from the Editor;
if (GIsEditor && !GEnableVREditorHacks)
{
Settings->BaseOrientation = FQuat::Identity;
Settings->BaseOffset = FVector::ZeroVector;
Settings->ColorScale = ovrpVector4f{ 1, 1, 1, 1 };
Settings->ColorOffset = ovrpVector4f{ 0, 0, 0, 0 };
//Settings->WorldToMetersScale = InWorldContext.World()->GetWorldSettings()->WorldToMeters;
//Settings->Flags.bWorldToMetersOverride = false;
InitDevice();
FApp::SetUseVRFocus(true);
FApp::SetHasVRFocus(true);
OnStartGameFrame(InWorldContext);
}
#endif
}
void FOculusXRHMD::OnEndPlay(FWorldContext& InWorldContext)
{
CheckInGameThread();
#if WITH_EDITOR
if (GIsEditor && !GEnableVREditorHacks)
{
// @todo vreditor: If we add support for starting PIE while in VR Editor, we don't want to kill stereo mode when exiting PIE
if (Splash->IsShown())
{
Splash->HideLoadingScreen(); // This will only request hiding the screen
Splash->UpdateLoadingScreen_GameThread(); // Update is needed to complete removing the loading screen
}
EnableStereo(false);
ReleaseDevice();
FApp::SetUseVRFocus(false);
FApp::SetHasVRFocus(false);
}
#endif
}
DECLARE_STATS_GROUP(TEXT("Oculus System Metrics"), STATGROUP_OculusSystemMetrics, STATCAT_Advanced);
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("App CPU Time (ms)"), STAT_OculusSystem_AppCpuTime, STATGROUP_OculusSystemMetrics, );
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("App GPU Time (ms)"), STAT_OculusSystem_AppGpuTime, STATGROUP_OculusSystemMetrics, );
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("Compositor CPU Time (ms)"), STAT_OculusSystem_ComCpuTime, STATGROUP_OculusSystemMetrics, );
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("Compositor GPU Time (ms)"), STAT_OculusSystem_ComGpuTime, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Compositor Dropped Frames"), STAT_OculusSystem_DroppedFrames, STATGROUP_OculusSystemMetrics, );
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("System GPU Util %"), STAT_OculusSystem_GpuUtil, STATGROUP_OculusSystemMetrics, );
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("System CPU Util Avg %"), STAT_OculusSystem_CpuUtilAvg, STATGROUP_OculusSystemMetrics, );
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("System CPU Util Worst %"), STAT_OculusSystem_CpuUtilWorst, STATGROUP_OculusSystemMetrics, );
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("CPU Clock Freq (MHz)"), STAT_OculusSystem_CpuFreq, STATGROUP_OculusSystemMetrics, );
DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("GPU Clock Freq (MHz)"), STAT_OculusSystem_GpuFreq, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Clock Level"), STAT_OculusSystem_CpuClockLvl, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("GPU Clock Level"), STAT_OculusSystem_GpuClockLvl, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("SpaceWarp Mode"), STAT_OculusSystem_ComSpaceWarpMode, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core0 Util %"), STAT_OculusSystem_CpuCore0Util, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core1 Util %"), STAT_OculusSystem_CpuCore1Util, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core2 Util %"), STAT_OculusSystem_CpuCore2Util, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core3 Util %"), STAT_OculusSystem_CpuCore3Util, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core4 Util %"), STAT_OculusSystem_CpuCore4Util, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core5 Util %"), STAT_OculusSystem_CpuCore5Util, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core6 Util %"), STAT_OculusSystem_CpuCore6Util, STATGROUP_OculusSystemMetrics, );
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core7 Util %"), STAT_OculusSystem_CpuCore7Util, STATGROUP_OculusSystemMetrics, );
DEFINE_STAT(STAT_OculusSystem_AppCpuTime);
DEFINE_STAT(STAT_OculusSystem_AppGpuTime);
DEFINE_STAT(STAT_OculusSystem_ComCpuTime);
DEFINE_STAT(STAT_OculusSystem_ComGpuTime);
DEFINE_STAT(STAT_OculusSystem_DroppedFrames);
DEFINE_STAT(STAT_OculusSystem_GpuUtil);
DEFINE_STAT(STAT_OculusSystem_CpuUtilAvg);
DEFINE_STAT(STAT_OculusSystem_CpuUtilWorst);
DEFINE_STAT(STAT_OculusSystem_CpuFreq);
DEFINE_STAT(STAT_OculusSystem_GpuFreq);
DEFINE_STAT(STAT_OculusSystem_CpuClockLvl);
DEFINE_STAT(STAT_OculusSystem_GpuClockLvl);
DEFINE_STAT(STAT_OculusSystem_ComSpaceWarpMode);
DEFINE_STAT(STAT_OculusSystem_CpuCore0Util);
DEFINE_STAT(STAT_OculusSystem_CpuCore1Util);
DEFINE_STAT(STAT_OculusSystem_CpuCore2Util);
DEFINE_STAT(STAT_OculusSystem_CpuCore3Util);
DEFINE_STAT(STAT_OculusSystem_CpuCore4Util);
DEFINE_STAT(STAT_OculusSystem_CpuCore5Util);
DEFINE_STAT(STAT_OculusSystem_CpuCore6Util);
DEFINE_STAT(STAT_OculusSystem_CpuCore7Util);
void UpdateOculusSystemMetricsStats()
{
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() == ovrpBool_False)
{
return;
}
ovrpBool bIsSupported;
float valueFloat = 0;
int valueInt = 0;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_App_CpuTime_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_App_CpuTime_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_AppCpuTime, valueFloat * 1000);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_App_GpuTime_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_App_GpuTime_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_AppGpuTime, valueFloat * 1000);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Compositor_CpuTime_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Compositor_CpuTime_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_ComCpuTime, valueFloat * 1000);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Compositor_GpuTime_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Compositor_GpuTime_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_ComGpuTime, valueFloat * 1000);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Compositor_DroppedFrameCount_Int, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsInt(ovrpPerfMetrics_Compositor_DroppedFrameCount_Int, &valueInt)))
{
SET_DWORD_STAT(STAT_OculusSystem_DroppedFrames, valueInt);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_System_GpuUtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_System_GpuUtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_GpuUtil, valueFloat * 100);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_System_CpuUtilAveragePercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_System_CpuUtilAveragePercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuUtilAvg, valueFloat * 100);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_System_CpuUtilWorstPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_System_CpuUtilWorstPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuUtilWorst, valueFloat * 100);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuClockFrequencyInMHz_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuClockFrequencyInMHz_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuFreq, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_GpuClockFrequencyInMHz_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_GpuClockFrequencyInMHz_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_GpuFreq, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuClockLevel_Int, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsInt(ovrpPerfMetrics_Device_CpuClockLevel_Int, &valueInt)))
{
SET_DWORD_STAT(STAT_OculusSystem_CpuClockLvl, valueInt);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_GpuClockLevel_Int, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsInt(ovrpPerfMetrics_Device_GpuClockLevel_Int, &valueInt)))
{
SET_DWORD_STAT(STAT_OculusSystem_GpuClockLvl, valueInt);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Compositor_SpaceWarp_Mode_Int, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsInt(ovrpPerfMetrics_Compositor_SpaceWarp_Mode_Int, &valueInt)))
{
SET_DWORD_STAT(STAT_OculusSystem_ComSpaceWarpMode, valueInt);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuCore0Util, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore1UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore1UtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuCore1Util, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore2UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore2UtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuCore2Util, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore3UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore3UtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuCore3Util, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore4UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore4UtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuCore4Util, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore5UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore5UtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuCore5Util, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore6UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore6UtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuCore6Util, valueFloat);
}
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore7UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore7UtilPercentage_Float, &valueFloat)))
{
SET_FLOAT_STAT(STAT_OculusSystem_CpuCore7Util, valueFloat);
}
}
}
void FOculusXRHMD::OnBeginRendering_GameThread()
{
CheckInGameThread();
// We need to make sure we keep the Wait/Begin/End triplet in sync, so here we signal that we
// can wait for the next frame in the next tick. Without this signal it's possible that two ticks
// happen before the next frame is actually rendered.
bShouldWait_GameThread = true;
}
bool FOculusXRHMD::OnStartGameFrame(FWorldContext& InWorldContext)
{
#if WITH_EDITOR
// In the editor there can be multiple worlds. An editor world, pie worlds, other viewport worlds for editor pages.
// XR hardware can only be running with one of them.
if (GIsEditor && GEditor && GEditor->GetPIEWorldContext() != nullptr)
{
if (!InWorldContext.bIsPrimaryPIEInstance)
{
return false;
}
}
#endif // WITH_EDITOR
CheckInGameThread();
if (IsEngineExitRequested())
{
return false;
}
UpdateOculusSystemMetricsStats();
RefreshTrackingToWorldTransform(InWorldContext);
// check if HMD is marked as invalid and needs to be killed.
ovrpBool appShouldRecreateDistortionWindow;
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppShouldRecreateDistortionWindow2(&appShouldRecreateDistortionWindow)) && appShouldRecreateDistortionWindow)
{
DoEnableStereo(false);
ReleaseDevice();
if (!OCFlags.DisplayLostDetected)
{
FCoreDelegates::VRHeadsetLost.Broadcast();
OCFlags.DisplayLostDetected = true;
}
Flags.bNeedEnableStereo = true;
}
#if PLATFORM_ANDROID
Flags.bNeedEnableStereo = true; // !!!
#endif
check(Settings.IsValid());
if (!Settings->IsStereoEnabled())
{
FApp::SetUseVRFocus(false);
FApp::SetHasVRFocus(false);
}
#if OCULUS_STRESS_TESTS_ENABLED
FStressTester::TickCPU_GameThread(this);
#endif
if (bShutdownRequestQueued)
{
bShutdownRequestQueued = false;
DoSessionShutdown();
}
if (!InWorldContext.World() || (!(GEnableVREditorHacks && InWorldContext.WorldType == EWorldType::Editor) && !InWorldContext.World()->IsGameWorld())) // @todo vreditor: (Also see OnEndGameFrame()) Kind of a hack here so we can use VR in editor viewports. We need to consider when running GameWorld viewports inside the editor with VR.
{
// ignore all non-game worlds
return false;
}
bool bStereoEnabled = Settings->Flags.bStereoEnabled;
bool bStereoDesired = bStereoEnabled;
if (Flags.bNeedEnableStereo)
{
bStereoDesired = true;
}
if (bStereoDesired && (Flags.bNeedDisableStereo || !Settings->Flags.bHMDEnabled))
{
bStereoDesired = false;
}
bool bStereoDesiredAndIsConnected = bStereoDesired;
if (bStereoDesired && !(bStereoEnabled ? IsHMDActive() : IsHMDEnabled()))
{
bStereoDesiredAndIsConnected = false;
}
Flags.bNeedEnableStereo = false;
Flags.bNeedDisableStereo = false;
if (bStereoEnabled != bStereoDesiredAndIsConnected)
{
bStereoEnabled = DoEnableStereo(bStereoDesiredAndIsConnected);
}
// Keep trying to enable stereo until we succeed
Flags.bNeedEnableStereo = bStereoDesired && !bStereoEnabled;
if (!Settings->IsStereoEnabled() && !Settings->Flags.bHeadTrackingEnforced)
{
return false;
}
if (Flags.bApplySystemOverridesOnStereo)
{
ApplySystemOverridesOnStereo();
Flags.bApplySystemOverridesOnStereo = false;
}
CachedWorldToMetersScale = InWorldContext.World()->GetWorldSettings()->WorldToMeters;
// this should have already happened in FOculusXRInput, so this is usually a no-op.
StartGameFrame_GameThread();
bool retval = true;
UpdateHMDEvents();
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
if (OCFlags.DisplayLostDetected)
{
FCoreDelegates::VRHeadsetReconnected.Broadcast();
OCFlags.DisplayLostDetected = false;
}
if (OCFlags.NeedSetTrackingOrigin)
{
SetTrackingOrigin(TrackingOrigin);
}
ovrpBool bAppHasVRFocus = ovrpBool_False;
FOculusXRHMDModule::GetPluginWrapper().GetAppHasVrFocus2(&bAppHasVRFocus);
FApp::SetUseVRFocus(true);
FApp::SetHasVRFocus(bAppHasVRFocus != ovrpBool_False);
// Do not pause if Editor is running (otherwise it will become very laggy)
if (!GIsEditor)
{
if (!bAppHasVRFocus)
{
// not visible,
if (!Settings->Flags.bPauseRendering)
{
UE_LOG(LogHMD, Log, TEXT("The app went out of VR focus, seizing rendering..."));
}
}
else if (Settings->Flags.bPauseRendering)
{
UE_LOG(LogHMD, Log, TEXT("The app got VR focus, restoring rendering..."));
}
if (OCFlags.NeedSetFocusToGameViewport)
{
if (bAppHasVRFocus)
{
UE_LOG(LogHMD, Log, TEXT("Setting user focus to game viewport since session status is visible..."));
FSlateApplication::Get().SetAllUserFocusToGameViewport();
OCFlags.NeedSetFocusToGameViewport = false;
}
}
bool bPrevPause = Settings->Flags.bPauseRendering;
Settings->Flags.bPauseRendering = !bAppHasVRFocus;
if (Settings->Flags.bPauseRendering && (GEngine->GetMaxFPS() != OCULUS_PAUSED_IDLE_FPS))
{
GEngine->SetMaxFPS(OCULUS_PAUSED_IDLE_FPS);
}
if (bPrevPause != Settings->Flags.bPauseRendering)
{
APlayerController* const PC = GEngine->GetFirstLocalPlayerController(InWorldContext.World());
if (Settings->Flags.bPauseRendering)
{
// focus is lost
GEngine->SetMaxFPS(OCULUS_PAUSED_IDLE_FPS);
if (!FCoreDelegates::ApplicationWillEnterBackgroundDelegate.IsBound())
{
OCFlags.AppIsPaused = false;
// default action: set pause if not already paused
if (PC && !PC->IsPaused())
{
PC->SetPause(true);
OCFlags.AppIsPaused = true;
}
}
else
{
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.Broadcast();
}
}
else
{
// focus is gained
GEngine->SetMaxFPS(0);
if (!FCoreDelegates::ApplicationHasEnteredForegroundDelegate.IsBound())
{
// default action: unpause if was paused by the plugin
if (PC && OCFlags.AppIsPaused)
{
PC->SetPause(false);
}
OCFlags.AppIsPaused = false;
}
else
{
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.Broadcast();
}
}
}
}
ovrpBool AppShouldQuit;
ovrpBool AppShouldRecenter;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppShouldQuit2(&AppShouldQuit)) && AppShouldQuit || OCFlags.EnforceExit)
{
FPlatformMisc::LowLevelOutputDebugString(TEXT("OculusXRHMD plugin requested exit (ShouldQuit == 1)\n"));
#if WITH_EDITOR
if (GIsEditor)
{
FSceneViewport* SceneVP = FindSceneViewport();
if (SceneVP && SceneVP->IsStereoRenderingAllowed())
{
TSharedPtr<SWindow> Window = SceneVP->FindWindow();
Window->RequestDestroyWindow();
}
}
else
#endif //WITH_EDITOR
{
// ApplicationWillTerminateDelegate will fire from inside of the RequestExit
FPlatformMisc::RequestExit(false);
}
OCFlags.EnforceExit = false;
retval = false;
}
else if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppShouldRecenter2(&AppShouldRecenter)) && AppShouldRecenter)
{
FPlatformMisc::LowLevelOutputDebugString(TEXT("OculusXRHMD plugin was requested to recenter\n"));
if (FCoreDelegates::VRHeadsetRecenter.IsBound())
{
FCoreDelegates::VRHeadsetRecenter.Broadcast();
}
else
{
ResetOrientationAndPosition();
}
// Call FOculusXRHMDModule::GetPluginWrapper().RecenterTrackingOrigin2 to clear AppShouldRecenter flag
FOculusXRHMDModule::GetPluginWrapper().RecenterTrackingOrigin2(ovrpRecenterFlag_IgnoreAll);
}
UpdateHMDWornState();
}
#if OCULUS_MR_SUPPORTED_PLATFORMS
if (FOculusXRHMDModule::GetPluginWrapper().GetMixedRealityInitialized())
{
FOculusXRHMDModule::GetPluginWrapper().UpdateExternalCamera();
}
#endif
if (IsEngineExitRequested())
{
PreShutdown();
}
return retval;
}
void FOculusXRHMD::DoSessionShutdown()
{
// Release resources
ExecuteOnRenderThread([this]() {
ExecuteOnRHIThread([this]() {
for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++)
{
Layers_RenderThread[LayerIndex]->ReleaseResources_RHIThread();
}
for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++)
{
Layers_RHIThread[LayerIndex]->ReleaseResources_RHIThread();
}
if (Splash.IsValid())
{
Splash->ReleaseResources_RHIThread();
}
if (CustomPresent)
{
CustomPresent->ReleaseResources_RHIThread();
}
Settings_RHIThread.Reset();
Frame_RHIThread.Reset();
Layers_RHIThread.Reset();
});
Settings_RenderThread.Reset();
Frame_RenderThread.Reset();
Layers_RenderThread.Reset();
EyeLayer_RenderThread.Reset();
DeferredDeletion.HandleLayerDeferredDeletionQueue_RenderThread(true);
EnableInsightPassthrough_RenderThread(false);
});
Frame.Reset();
NextFrameToRender.Reset();
LastFrameToRender.Reset();
#if !UE_BUILD_SHIPPING
UDebugDrawService::Unregister(DrawDebugDelegateHandle);
#endif
// The Editor may release VR focus in OnEndPlay
if (!GIsEditor)
{
FApp::SetUseVRFocus(false);
FApp::SetHasVRFocus(false);
}
ShutdownSession();
}
bool FOculusXRHMD::OnEndGameFrame(FWorldContext& InWorldContext)
{
CheckInGameThread();
FGameFrame* const CurrentGameFrame = Frame.Get();
if (CurrentGameFrame)
{
// don't use the cached value, as it could be affected by the player's position, so we update it here at the latest point in the game frame
CurrentGameFrame->TrackingToWorld = ComputeTrackingToWorldTransform(InWorldContext);
CurrentGameFrame->LastTrackingToWorld = LastTrackingToWorld;
LastTrackingToWorld = CurrentGameFrame->TrackingToWorld;
}
else
{
return false;
}
if (!InWorldContext.World() || (!(GEnableVREditorHacks && InWorldContext.WorldType == EWorldType::Editor) && !InWorldContext.World()->IsGameWorld()))
{
// ignore all non-game worlds
return false;
}
FinishGameFrame_GameThread();
return true;
}
FVector2D FOculusXRHMD::GetPlayAreaBounds(EHMDTrackingOrigin::Type Origin) const
{
ovrpVector3f Dimensions;
if (Origin == EHMDTrackingOrigin::Stage && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetBoundaryDimensions2(ovrpBoundary_PlayArea, &Dimensions)))
{
Dimensions.z *= -1.0;
FVector Bounds = ConvertVector_M2U(Dimensions);
return FVector2D(Bounds.X, Bounds.Z);
}
return FVector2D::ZeroVector;
}
bool FOculusXRHMD::IsHMDEnabled() const
{
CheckInGameThread();
return (Settings->Flags.bHMDEnabled);
}
EHMDWornState::Type FOculusXRHMD::GetHMDWornState()
{
ovrpBool userPresent;
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserPresent2(&userPresent)) && userPresent)
{
return EHMDWornState::Worn;
}
else
{
return EHMDWornState::NotWorn;
}
}
void FOculusXRHMD::EnableHMD(bool enable)
{
CheckInGameThread();
Settings->Flags.bHMDEnabled = enable;
if (!Settings->Flags.bHMDEnabled)
{
EnableStereo(false);
}
}
bool FOculusXRHMD::GetHMDMonitorInfo(MonitorInfo& MonitorDesc)
{
CheckInGameThread();
MonitorDesc.MonitorName = FString("Oculus Window");
MonitorDesc.MonitorId = 0;
MonitorDesc.DesktopX = MonitorDesc.DesktopY = 0;
MonitorDesc.ResolutionX = MonitorDesc.ResolutionY = 0;
MonitorDesc.WindowSizeX = MonitorDesc.WindowSizeY = 0;
if (Settings.IsValid())
{
MonitorDesc.ResolutionX = MonitorDesc.WindowSizeX = Settings->RenderTargetSize.X;
MonitorDesc.ResolutionY = MonitorDesc.WindowSizeY = Settings->RenderTargetSize.Y;
}
return true;
}
void FOculusXRHMD::GetFieldOfView(float& InOutHFOVInDegrees, float& InOutVFOVInDegrees) const
{
ovrpFrustum2f Frustum;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodeFrustum2(ovrpNode_EyeCenter, &Frustum)))
{
InOutVFOVInDegrees = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.UpTan) + FMath::Atan(Frustum.Fov.DownTan));
InOutHFOVInDegrees = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.LeftTan) + FMath::Atan(Frustum.Fov.RightTan));
}
}
void FOculusXRHMD::SetInterpupillaryDistance(float NewInterpupillaryDistance)
{
CheckInGameThread();
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
FOculusXRHMDModule::GetPluginWrapper().SetUserIPD2(NewInterpupillaryDistance);
}
}
float FOculusXRHMD::GetInterpupillaryDistance() const
{
CheckInGameThread();
float UserIPD;
if (!FOculusXRHMDModule::GetPluginWrapper().GetInitialized() || OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetUserIPD2(&UserIPD)))
{
return 0.0f;
}
return UserIPD;
}
bool FOculusXRHMD::GetHMDDistortionEnabled(EShadingPath /* ShadingPath */) const
{
return false;
}
bool FOculusXRHMD::IsChromaAbCorrectionEnabled() const
{
CheckInGameThread();
return true;
}
bool FOculusXRHMD::HasHiddenAreaMesh() const
{
if (IsInParallelRenderingThread())
{
if (ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread())
{
return false;
}
}
return HiddenAreaMeshes[0].IsValid() && HiddenAreaMeshes[1].IsValid();
}
bool FOculusXRHMD::HasVisibleAreaMesh() const
{
if (IsInParallelRenderingThread())
{
if (ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread())
{
return false;
}
}
return VisibleAreaMeshes[0].IsValid() && VisibleAreaMeshes[1].IsValid();
}
static void DrawOcclusionMesh(FRHICommandList& RHICmdList, int32 ViewIndex, const FHMDViewMesh MeshAssets[])
{
check(ViewIndex != INDEX_NONE);
const uint32 MeshIndex = (ViewIndex == EStereoscopicEye::eSSE_LEFT_EYE) ? 0 : 1;
const FHMDViewMesh& Mesh = MeshAssets[MeshIndex];
check(Mesh.IsValid());
RHICmdList.SetStreamSource(0, Mesh.VertexBufferRHI, 0);
RHICmdList.DrawIndexedPrimitive(Mesh.IndexBufferRHI, 0, 0, Mesh.NumVertices, 0, Mesh.NumTriangles, 1);
}
void FOculusXRHMD::DrawHiddenAreaMesh(FRHICommandList& RHICmdList, int32 ViewIndex) const
{
DrawOcclusionMesh(RHICmdList, ViewIndex, HiddenAreaMeshes);
}
void FOculusXRHMD::DrawVisibleAreaMesh(FRHICommandList& RHICmdList, int32 ViewIndex) const
{
DrawOcclusionMesh(RHICmdList, ViewIndex, VisibleAreaMeshes);
}
float FOculusXRHMD::GetPixelDenity() const
{
if (IsInGameThread())
{
return Settings.IsValid() ? Settings->PixelDensity : 1.0f;
}
else
{
return Settings_RenderThread.IsValid() ? Settings_RenderThread->PixelDensity : 1.0f;
}
}
void FOculusXRHMD::SetPixelDensity(const float NewPixelDensity)
{
CheckInGameThread();
Settings->SetPixelDensity(NewPixelDensity);
}
FIntPoint FOculusXRHMD::GetIdealRenderTargetSize() const
{
if (IsInGameThread())
{
return Settings.IsValid() ? Settings->RenderTargetSize : 1.0f;
}
else
{
return Settings_RenderThread.IsValid() ? Settings_RenderThread->RenderTargetSize : 1.0f;
}
}
void FOculusXRHMD::GetMotionControllerData(UObject* WorldContext, const EControllerHand Hand, FXRMotionControllerData& MotionControllerData)
{
MotionControllerData.DeviceName = OculusSystemName;
MotionControllerData.ApplicationInstanceID = FApp::GetInstanceId();
MotionControllerData.DeviceVisualType = EXRVisualType::Controller;
MotionControllerData.TrackingStatus = ETrackingStatus::NotTracked;
MotionControllerData.HandIndex = Hand;
MotionControllerData.bValid = false;
if ((Hand == EControllerHand::Left) || (Hand == EControllerHand::Right))
{
const FName MotionControllerName("OculusXRInputDevice");
TArray<IMotionController*> MotionControllers = IModularFeatures::Get().GetModularFeatureImplementations<IMotionController>(IMotionController::GetModularFeatureName());
const IMotionController* MotionController = nullptr;
for (const IMotionController* Itr : MotionControllers)
{
if (Itr->GetMotionControllerDeviceTypeName() == MotionControllerName)
{
MotionController = Itr;
break;
}
}
const float WorldToMeters = GetWorldToMetersScale();
if (MotionController)
{
bool bSuccess = false;
FVector Position = FVector::ZeroVector;
FRotator Rotation = FRotator::ZeroRotator;
const FTransform TrackingToWorld = GetTrackingToWorldTransform();
const FName AimSource = Hand == EControllerHand::Left ? FName("LeftAim") : FName("RightAim");
bSuccess = MotionController->GetControllerOrientationAndPosition(0, AimSource, Rotation, Position, WorldToMeters);
if (bSuccess)
{
MotionControllerData.AimPosition = TrackingToWorld.TransformPosition(Position);
MotionControllerData.AimRotation = TrackingToWorld.TransformRotation(FQuat(Rotation));
}
MotionControllerData.bValid |= bSuccess;
FName GripSource = Hand == EControllerHand::Left ? FName("LeftGrip") : FName("RightGrip");
bSuccess = MotionController->GetControllerOrientationAndPosition(0, GripSource, Rotation, Position, WorldToMeters);
if (bSuccess)
{
MotionControllerData.GripPosition = TrackingToWorld.TransformPosition(Position);
MotionControllerData.GripRotation = TrackingToWorld.TransformRotation(FQuat(Rotation));
}
MotionControllerData.bValid |= bSuccess;
MotionControllerData.TrackingStatus = MotionController->GetControllerTrackingStatus(0, GripSource);
}
}
}
bool FOculusXRHMD::IsStereoEnabled() const
{
if (IsInGameThread())
{
return Settings.IsValid() && Settings->IsStereoEnabled();
}
else
{
return Settings_RenderThread.IsValid() && Settings_RenderThread->IsStereoEnabled();
}
}
bool FOculusXRHMD::IsStereoEnabledOnNextFrame() const
{
// !!!
return Settings.IsValid() && Settings->IsStereoEnabled();
}
bool FOculusXRHMD::EnableStereo(bool bStereo)
{
CheckInGameThread();
if (bStereo)
{
LoadFromSettings();
CheckMultiPlayer();
}
return DoEnableStereo(bStereo);
}
void FOculusXRHMD::AdjustViewRect(int32 ViewIndex, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const
{
if (Settings.IsValid())
{
X = Settings->EyeUnscaledRenderViewport[ViewIndex].Min.X;
Y = Settings->EyeUnscaledRenderViewport[ViewIndex].Min.Y;
SizeX = Settings->EyeUnscaledRenderViewport[ViewIndex].Size().X;
SizeY = Settings->EyeUnscaledRenderViewport[ViewIndex].Size().Y;
}
else
{
SizeX = SizeX / 2;
X += SizeX * ViewIndex;
}
}
FIntRect FOculusXRHMD::GetAsymmetricViewRect(const int32 ViewIndex, const FIntRect& ViewRect)
{
FIntRect AsymmetricViewRect = ViewRect;
if (Settings_RenderThread.IsValid() && Frame_RenderThread.IsValid())
{
const ovrpFovf& EyeBufferFov = Frame_RenderThread->Fov[ViewIndex];
const ovrpFovf& FrameFov = Frame_RenderThread->SymmetricFov[ViewIndex];
const int32 ViewPixelSize = AsymmetricViewRect.Size().X;
// if using symmetric rendering, only send UVs of the asymmetrical subrect (the rest isn't useful) to the VR runtime
const float symTanSize = FrameFov.LeftTan + FrameFov.RightTan;
AsymmetricViewRect.Min.X += (FrameFov.LeftTan - EyeBufferFov.LeftTan) * (ViewPixelSize / symTanSize);
AsymmetricViewRect.Max.X -= (FrameFov.RightTan - EyeBufferFov.RightTan) * (ViewPixelSize / symTanSize);
}
return AsymmetricViewRect;
}
void FOculusXRHMD::SetFinalViewRect(FRHICommandListImmediate& RHICmdList, const int32 ViewIndex, const FIntRect& FinalViewRect)
{
CheckInRenderThread();
if (ViewIndex == INDEX_NONE || ViewIndex < 0 || ViewIndex >= ovrpEye_Count)
{
return;
}
FIntRect AsymmetricViewRect = GetAsymmetricViewRect(ViewIndex, FinalViewRect);
if (Settings_RenderThread.IsValid())
{
Settings_RenderThread->EyeRenderViewport[ViewIndex] = AsymmetricViewRect;
}
// Called after RHIThread has already started. Need to update Settings_RHIThread as well.
ExecuteOnRHIThread_DoNotWait([this, ViewIndex, AsymmetricViewRect]() {
CheckInRHIThread();
if (Settings_RHIThread.IsValid())
{
Settings_RHIThread->EyeRenderViewport[ViewIndex] = AsymmetricViewRect;
}
});
}
#ifdef WITH_OCULUS_BRANCH
void FOculusXRHMD::CalculateScissorRect(const int32 ViewIndex, const FIntRect& ViewRect, FIntRect& OutRect)
{
CheckInRenderThread();
if (ViewIndex == INDEX_NONE || ViewIndex < 0 || ViewIndex >= ovrpEye_Count)
{
return;
}
OutRect = GetAsymmetricViewRect(ViewIndex, ViewRect);
}
#endif // WITH_OCULUS_BRANCH
void FOculusXRHMD::CalculateStereoViewOffset(const int32 ViewIndex, FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation)
{
// This method is called from GetProjectionData on a game thread.
if (InGameThread() && ViewIndex == EStereoscopicEye::eSSE_LEFT_EYE && NextFrameToRender.IsValid())
{
// Inverse out GameHeadPose.Rotation since PlayerOrientation already contains head rotation.
FQuat HeadOrientation = FQuat::Identity;
FVector HeadPosition;
GetCurrentPose(HMDDeviceId, HeadOrientation, HeadPosition);
NextFrameToRender->HeadOrientation = HeadOrientation;
NextFrameToRender->PlayerOrientation = LastPlayerOrientation = ViewRotation.Quaternion() * HeadOrientation.Inverse();
NextFrameToRender->PlayerLocation = LastPlayerLocation = ViewLocation;
}
FHeadMountedDisplayBase::CalculateStereoViewOffset(ViewIndex, ViewRotation, WorldToMeters, ViewLocation);
}
FMatrix FOculusXRHMD::GetStereoProjectionMatrix(int32 ViewIndex) const
{
CheckInGameThread();
check(IsStereoEnabled());
FMatrix proj = (ViewIndex == EStereoscopicEye::eSSE_MONOSCOPIC) ? ToFMatrix(Settings->MonoProjectionMatrix) : ToFMatrix(Settings->EyeProjectionMatrices[ViewIndex]);
// correct far and near planes for reversed-Z projection matrix
const float WorldScale = GetWorldToMetersScale() * (1.0 / 100.0f); // physical scale is 100 UUs/meter
float InNearZ = GNearClippingPlane * WorldScale;
proj.M[3][3] = 0.0f;
proj.M[2][3] = 1.0f;
proj.M[2][2] = 0.0f;
proj.M[3][2] = InNearZ;
return proj;
}
void FOculusXRHMD::InitCanvasFromView(FSceneView* InView, UCanvas* Canvas)
{
// This is used for placing small HUDs (with names)
// over other players (for example, in Capture Flag).
// HmdOrientation should be initialized by GetCurrentOrientation (or
// user's own value).
}
void FOculusXRHMD::RenderTexture_RenderThread(class FRHICommandListImmediate& RHICmdList, class FRHITexture* BackBuffer, class FRHITexture* SrcTexture, FVector2D WindowSize) const
{
CheckInRenderThread();
check(CustomPresent);
#if PLATFORM_ANDROID
return;
#endif
if (SpectatorScreenController)
{
SpectatorScreenController->RenderSpectatorScreen_RenderThread(RHICmdList, BackBuffer, SrcTexture, WindowSize);
}
}
FVector2D FOculusXRHMD::GetEyeCenterPoint_RenderThread(int32 ViewIndex) const
{
CheckInRenderThread();
check(IsStereoEnabled() || IsHeadTrackingEnforced());
// Don't use GetStereoProjectionMatrix because it is game thread only on oculus, we also don't need the zplane adjustments for this.
const FMatrix StereoProjectionMatrix = ToFMatrix(Settings_RenderThread->EyeProjectionMatrices[ViewIndex]);
//0,0,1 is the straight ahead point, wherever it maps to is the center of the projection plane in -1..1 coordinates. -1,-1 is bottom left.
const FVector4 ScreenCenter = StereoProjectionMatrix.TransformPosition(FVector(0.0f, 0.0f, 1.0f));
//transform into 0-1 screen coordinates 0,0 is top left.
const FVector2D CenterPoint(0.5f + (ScreenCenter.X / 2.0f), 0.5f - (ScreenCenter.Y / 2.0f));
return CenterPoint;
}
FIntRect FOculusXRHMD::GetFullFlatEyeRect_RenderThread(FTexture2DRHIRef EyeTexture) const
{
CheckInRenderThread();
// Rift does this differently than other platforms, it already has an idea of what rectangle it wants to use stored.
FIntRect& EyeRect = Settings_RenderThread->EyeRenderViewport[0];
// But the rectangle rift specifies has corners cut off, so we will crop a little more.
if (ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread())
{
return EyeRect;
}
else
{
static FVector2D SrcNormRectMin(0.05f, 0.0f);
static FVector2D SrcNormRectMax(0.95f, 1.0f);
const int32 SizeX = EyeRect.Max.X - EyeRect.Min.X;
const int32 SizeY = EyeRect.Max.Y - EyeRect.Min.Y;
return FIntRect(EyeRect.Min.X + SizeX * SrcNormRectMin.X, EyeRect.Min.Y + SizeY * SrcNormRectMin.Y, EyeRect.Min.X + SizeX * SrcNormRectMax.X, EyeRect.Min.Y + SizeY * SrcNormRectMax.Y);
}
}
void FOculusXRHMD::CopyTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture2D* SrcTexture, FIntRect SrcRect, FRHITexture2D* DstTexture, FIntRect DstRect, bool bClearBlack, bool bNoAlpha) const
{
if (bClearBlack)
{
FRHIRenderPassInfo RPInfo(DstTexture, ERenderTargetActions::DontLoad_Store);
RHICmdList.BeginRenderPass(RPInfo, TEXT("ClearToBlack"));
{
const FIntRect ClearRect(0, 0, DstTexture->GetSizeX(), DstTexture->GetSizeY());
RHICmdList.SetViewport(ClearRect.Min.X, ClearRect.Min.Y, 0, ClearRect.Max.X, ClearRect.Max.Y, 1.0f);
DrawClearQuad(RHICmdList, FLinearColor::Black);
}
RHICmdList.EndRenderPass();
}
check(CustomPresent);
CustomPresent->CopyTexture_RenderThread(RHICmdList, DstTexture, SrcTexture, DstRect, SrcRect, false, bNoAlpha, true, true);
}
bool FOculusXRHMD::PopulateAnalyticsAttributes(TArray<FAnalyticsEventAttribute>& EventAttributes)
{
if (!FHeadMountedDisplayBase::PopulateAnalyticsAttributes(EventAttributes))
{
return false;
}
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("HQBuffer"), (bool)Settings->Flags.bHQBuffer));
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("HQDistortion"), (bool)Settings->Flags.bHQDistortion));
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("UpdateOnRT"), (bool)Settings->Flags.bUpdateOnRT));
return true;
}
bool FOculusXRHMD::ShouldUseSeparateRenderTarget() const
{
return IsStereoEnabled();
}
void FOculusXRHMD::CalculateRenderTargetSize(const FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY)
{
// TODO this should use Settings_RenderThread if !CheckInGameThread()
// This is called before StartRenderFrame_GameThread() on startup
if (!Settings->IsStereoEnabled())
{
return;
}
InOutSizeX = Settings->RenderTargetSize.X;
InOutSizeY = Settings->RenderTargetSize.Y;
check(InOutSizeX != 0 && InOutSizeY != 0);
}
void FOculusXRHMD::AllocateEyeBuffer()
{
CheckInGameThread();
ExecuteOnRenderThread([&]() {
InitializeEyeLayer_RenderThread(GetImmediateCommandList_ForRenderCommand());
const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetSwapChain();
if (SwapChain.IsValid())
{
const FRHITexture2D* const SwapChainTexture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
UE_LOG(LogHMD, Log, TEXT("Allocating Oculus %d x %d rendertarget swapchain"), SwapChainTexture->GetSizeX(), SwapChainTexture->GetSizeY());
}
});
bNeedReAllocateViewportRenderTarget = true;
}
bool FOculusXRHMD::NeedReAllocateViewportRenderTarget(const FViewport& Viewport)
{
CheckInGameThread();
return ensureMsgf(Settings.IsValid(), TEXT("Unexpected issue with Oculus settings on the GameThread. This should be valid when this is called in EnqueueBeginRenderFrame() - has the callsite changed?")) && Settings->IsStereoEnabled() && bNeedReAllocateViewportRenderTarget;
}
bool FOculusXRHMD::NeedReAllocateDepthTexture(const TRefCountPtr<IPooledRenderTarget>& DepthTarget)
{
CheckInRenderThread();
return ensureMsgf(Settings_RenderThread.IsValid(), TEXT("Unexpected issue with Oculus settings on the RenderThread. This should be valid when this is called in AllocateCommonDepthTargets() - has the callsite changed?")) && Settings_RenderThread->IsStereoEnabled() && bNeedReAllocateDepthTexture_RenderThread;
}
bool FOculusXRHMD::NeedReAllocateShadingRateTexture(const TRefCountPtr<IPooledRenderTarget>& FoveationTarget)
{
CheckInRenderThread();
return ensureMsgf(Settings_RenderThread.IsValid(), TEXT("Unexpected issue with Oculus settings on the RenderThread. This should be valid when this is called in AllocateFoveationTexture() - has the callsite changed?")) && Settings_RenderThread->IsStereoEnabled() && bNeedReAllocateFoveationTexture_RenderThread;
}
#ifdef WITH_OCULUS_BRANCH
bool FOculusXRHMD::NeedReAllocateMotionVectorTexture(const TRefCountPtr<IPooledRenderTarget>& MotionVectorTarget, const TRefCountPtr<IPooledRenderTarget>& MotionVectorDepthTarget)
{
CheckInRenderThread();
return ensureMsgf(Settings_RenderThread.IsValid(), TEXT("Unexpected issue with Oculus settings on the RenderThread. This should be valid when this is called in AllocateMotionVectorTexture() - has the callsite changed?")) && Settings_RenderThread->IsStereoEnabled() && bNeedReAllocateMotionVectorTexture_RenderThread;
}
#endif // WITH_OCULUS_BRANCH
bool FOculusXRHMD::AllocateRenderTargetTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples)
{
CheckInRenderThread();
check(Index == 0);
if (LayerMap[0].IsValid())
{
const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetSwapChain();
if (SwapChain.IsValid())
{
OutTargetableTexture = OutShaderResourceTexture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
bNeedReAllocateViewportRenderTarget = false;
return true;
}
}
OutTargetableTexture = OutShaderResourceTexture = nullptr;
return false;
}
bool FOculusXRHMD::AllocateDepthTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags FlagsIn, ETextureCreateFlags TargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples)
{
CheckInRenderThread();
check(Index == 0);
if (EyeLayer_RenderThread.IsValid())
{
const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetDepthSwapChain();
if (SwapChain.IsValid())
{
FTexture2DRHIRef Texture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
FIntPoint TexSize = Texture->GetSizeXY();
// Ensure the texture size matches the eye layer. We may get other depth allocations unrelated to the main scene render.
if (FIntPoint(SizeX, SizeY) == TexSize)
{
if (bNeedReAllocateDepthTexture_RenderThread)
{
UE_LOG(LogHMD, Log, TEXT("Allocating Oculus %d x %d depth rendertarget swapchain"), SizeX, SizeY);
bNeedReAllocateDepthTexture_RenderThread = false;
}
OutTargetableTexture = OutShaderResourceTexture = Texture;
return true;
}
}
}
OutTargetableTexture = OutShaderResourceTexture = nullptr;
return false;
}
bool FOculusXRHMD::AllocateShadingRateTexture(uint32 Index, uint32 RenderSizeX, uint32 RenderSizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTexture, FIntPoint& OutTextureSize)
{
CheckInRenderThread();
check(Index == 0);
if (EyeLayer_RenderThread.IsValid())
{
const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetFoveationSwapChain();
if (SwapChain.IsValid())
{
FTexture2DRHIRef Texture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
FIntPoint TexSize = Texture->GetSizeXY();
// Only set texture and return true if we have a valid texture of compatible size
if (Texture->IsValid() && TexSize.X > 0 && TexSize.Y > 0)
{
if (bNeedReAllocateFoveationTexture_RenderThread)
{
UE_LOG(LogHMD, Log, TEXT("Allocating Oculus %d x %d variable resolution swapchain"), TexSize.X, TexSize.Y, Index);
bNeedReAllocateFoveationTexture_RenderThread = false;
}
// This is a hack to turn force the runtime to use FDM over FSR when we allocate our FDM to avoid a crash on Quest 3
// TODO: Remove this for UE 5.3 after there's an engine-side fix
ExecuteOnRHIThread_DoNotWait([this]() {
// Set this in AllocateShadingRateTexture because it guarantees that this runs after VulkanExtensions has initially
// selected the shading rate type, before the FDM is actually going to be used, and only when we actually have an FDM
CustomPresent->UseFragmentDensityMapOverShadingRate_RHIThread();
});
OutTexture = Texture;
OutTextureSize = TexSize;
return true;
}
}
}
OutTexture = nullptr;
return false;
}
#ifdef WITH_OCULUS_BRANCH
bool FOculusXRHMD::AllocateMotionVectorTexture(uint32 Index, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTexture, FIntPoint& OutTextureSize, FTexture2DRHIRef& OutDepthTexture, FIntPoint& OutDepthTextureSize)
{
CheckInRenderThread();
check(Index == 0);
if (EyeLayer_RenderThread.IsValid())
{
const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetMotionVectorSwapChain();
if (SwapChain.IsValid())
{
FTexture2DRHIRef Texture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
FIntPoint TexSize = Texture->GetSizeXY();
const FXRSwapChainPtr& DepthSwapChain = EyeLayer_RenderThread->GetMotionVectorDepthSwapChain();
if (DepthSwapChain.IsValid())
{
FTexture2DRHIRef DepthTexture = DepthSwapChain->GetTexture2DArray() ? DepthSwapChain->GetTexture2DArray() : DepthSwapChain->GetTexture2D();
FIntPoint DepthTexSize = DepthTexture->GetSizeXY();
if (DepthTexture->IsValid() && DepthTexSize.X > 0 && DepthTexSize.Y > 0)
{
OutDepthTextureSize = DepthTexSize;
OutDepthTexture = DepthTexture;
}
else
{
return false;
}
}
// Only set texture and return true if we have a valid texture of compatible size
if (Texture->IsValid() && TexSize.X > 0 && TexSize.Y > 0)
{
if (bNeedReAllocateMotionVectorTexture_RenderThread)
{
UE_LOG(LogHMD, Log, TEXT("[Mobile SpaceWarp] Allocating Oculus %d x %d motion vector swapchain"), TexSize.X, TexSize.Y, Index);
bNeedReAllocateMotionVectorTexture_RenderThread = false;
}
OutTexture = Texture;
OutTextureSize = TexSize;
return true;
}
}
}
OutTexture = nullptr;
return false;
}
#endif // WITH_OCULUS_BRANCH
#if defined(WITH_OCULUS_BRANCH)
bool FOculusXRHMD::FindEnvironmentDepthTexture_RenderThread(FTextureRHIRef& OutTexture, FVector2f& OutDepthFactors, FMatrix44f OutScreenToDepthMatrices[2], FMatrix44f OutDepthViewProjMatrices[2])
{
CheckInRenderThread();
if (Frame_RenderThread.IsValid())
{
int SwapchainIndex;
if (ComputeEnvironmentDepthParameters_RenderThread(OutDepthFactors, OutScreenToDepthMatrices, OutDepthViewProjMatrices, SwapchainIndex))
{
if (SwapchainIndex >= EnvironmentDepthSwapchain.Num())
{
return false;
}
OutTexture = EnvironmentDepthSwapchain[SwapchainIndex];
return true;
}
}
return false;
}
#endif // defined(WITH_OCULUS_BRANCH)
EPixelFormat FOculusXRHMD::GetActualColorSwapchainFormat() const
{
if (!CustomPresent.IsValid())
{
UE_LOG(LogHMD, Log, TEXT("Invalid CustomPresent! PF_R8G8B8A8 will be used as the default swapchain format!"));
return PF_R8G8B8A8;
}
return CustomPresent->GetDefaultPixelFormat();
}
void FOculusXRHMD::UpdateViewportWidget(bool bUseSeparateRenderTarget, const class FViewport& Viewport, class SViewport* ViewportWidget)
{
CheckInGameThread();
check(ViewportWidget);
TSharedPtr<SWindow> Window = CachedWindow.Pin();
TSharedPtr<SWidget> CurrentlyCachedWidget = CachedViewportWidget.Pin();
TSharedRef<SWidget> Widget = ViewportWidget->AsShared();
if (!Window.IsValid() || Widget != CurrentlyCachedWidget)
{
Window = FSlateApplication::Get().FindWidgetWindow(Widget);
CachedViewportWidget = Widget;
CachedWindow = Window;
}
if (!Settings->IsStereoEnabled())
{
// Restore AutoResizeViewport mode for the window
if (Window.IsValid())
{
Window->SetMirrorWindow(false);
Window->SetViewportSizeDrivenByWindow(true);
}
return;
}
if (bUseSeparateRenderTarget && Frame.IsValid())
{
if (Window.IsValid())
{
const auto SlateWindowSize = Window->GetSizeInScreen();
CachedWindowSize = FIntPoint(static_cast<int>(SlateWindowSize.X), static_cast<int>(SlateWindowSize.Y));
}
else
{
CachedWindowSize = Viewport.GetSizeXY();
}
}
}
FXRRenderBridge* FOculusXRHMD::GetActiveRenderBridge_GameThread(bool bUseSeparateRenderTarget)
{
CheckInGameThread();
if (bUseSeparateRenderTarget && NextFrameToRender.IsValid())
{
return CustomPresent;
}
else
{
return nullptr;
}
}
void FOculusXRHMD::UpdateHMDWornState()
{
const EHMDWornState::Type NewHMDWornState = GetHMDWornState();
if (NewHMDWornState != HMDWornState)
{
HMDWornState = NewHMDWornState;
if (HMDWornState == EHMDWornState::Worn)
{
FCoreDelegates::VRHeadsetPutOnHead.Broadcast();
}
else if (HMDWornState == EHMDWornState::NotWorn)
{
FCoreDelegates::VRHeadsetRemovedFromHead.Broadcast();
}
}
}
void FOculusXRHMD::UpdateHMDEvents()
{
ovrpEventDataBuffer buf;
while (FOculusXRHMDModule::GetPluginWrapper().PollEvent(&buf) == ovrpSuccess)
{
if (buf.EventType == ovrpEventType_None)
{
break;
}
else if (buf.EventType == ovrpEventType_DisplayRefreshRateChange)
{
ovrpEventDisplayRefreshRateChange* rateChangedEvent = (ovrpEventDisplayRefreshRateChange*)&buf;
FOculusEventDelegates::OculusDisplayRefreshRateChanged.Broadcast(rateChangedEvent->FromRefreshRate, rateChangedEvent->ToRefreshRate);
}
else
{
for (auto& it : EventPollingDelegates)
{
bool HandledEvent = false;
it.ExecuteIfBound(&buf, HandledEvent);
}
}
}
}
uint32 FOculusXRHMD::CreateLayer(const IStereoLayers::FLayerDesc& InLayerDesc)
{
CheckInGameThread();
uint32 LayerId = NextLayerId++;
FLayerPtr Layer = MakeShareable(new FLayer(LayerId));
LayerMap.Add(LayerId, Layer);
Layer->SetDesc(Settings.Get(), InLayerDesc);
return LayerId;
}
void FOculusXRHMD::DestroyLayer(uint32 LayerId)
{
CheckInGameThread();
FLayerPtr* LayerFound = LayerMap.Find(LayerId);
if (LayerFound)
{
(*LayerFound)->DestroyLayer();
}
LayerMap.Remove(LayerId);
}
void FOculusXRHMD::SetLayerDesc(uint32 LayerId, const IStereoLayers::FLayerDesc& InLayerDesc)
{
CheckInGameThread();
FLayerPtr* LayerFound = LayerMap.Find(LayerId);
if (LayerFound)
{
FLayer* Layer = new FLayer(**LayerFound);
Layer->SetDesc(Settings.Get(), InLayerDesc);
*LayerFound = MakeShareable(Layer);
}
}
bool FOculusXRHMD::GetLayerDesc(uint32 LayerId, IStereoLayers::FLayerDesc& OutLayerDesc)
{
CheckInGameThread();
FLayerPtr* LayerFound = LayerMap.Find(LayerId);
if (LayerFound)
{
OutLayerDesc = (*LayerFound)->GetDesc();
return true;
}
return false;
}
void FOculusXRHMD::MarkTextureForUpdate(uint32 LayerId)
{
CheckInGameThread();
FLayerPtr* LayerFound = LayerMap.Find(LayerId);
if (LayerFound)
{
(*LayerFound)->MarkTextureForUpdate();
}
}
void FOculusXRHMD::SetSplashRotationToForward()
{
//if update splash screen is shown, update the head orientation default to recenter splash screens
FQuat HeadOrientation = FQuat::Identity;
FVector HeadPosition;
GetCurrentPose(HMDDeviceId, HeadOrientation, HeadPosition);
SplashRotation = FRotator(HeadOrientation);
SplashRotation.Pitch = 0;
SplashRotation.Roll = 0;
}
FOculusXRSplashDesc FOculusXRHMD::GetUESplashScreenDesc()
{
FOculusXRSplashDesc Desc;
Desc.LoadedTexture = bSplashShowMovie ? SplashMovie : SplashTexture;
Desc.TransformInMeters = Desc.TransformInMeters * FTransform(SplashOffset / GetWorldToMetersScale());
Desc.bNoAlphaChannel = true;
Desc.bIsDynamic = bSplashShowMovie;
Desc.QuadSizeInMeters *= SplashScale;
return Desc;
}
void FOculusXRHMD::EyeTrackedFoveatedRenderingFallback()
{
FoveatedRenderingMethod = EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering;
FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::High;
bDynamicFoveatedRendering = true;
}
void FOculusXRHMD::GetAllocatedTexture(uint32 LayerId, FTextureRHIRef& Texture, FTextureRHIRef& LeftTexture)
{
Texture = LeftTexture = nullptr;
FLayerPtr* LayerFound = nullptr;
if (IsInGameThread())
{
LayerFound = LayerMap.Find(LayerId);
}
else if (IsInParallelRenderingThread())
{
for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++)
{
if (Layers_RenderThread[LayerIndex]->GetId() == LayerId)
{
LayerFound = &Layers_RenderThread[LayerIndex];
}
}
}
else if (IsInRHIThread())
{
for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++)
{
if (Layers_RHIThread[LayerIndex]->GetId() == LayerId)
{
LayerFound = &Layers_RHIThread[LayerIndex];
}
}
}
else
{
return;
}
if (LayerFound && (*LayerFound)->GetSwapChain().IsValid())
{
bool bRightTexture = (*LayerFound)->GetRightSwapChain().IsValid();
const IStereoLayers::FLayerDesc& Desc = (*LayerFound)->GetDesc();
if (Desc.HasShape<FCubemapLayer>())
{
if (bRightTexture)
{
Texture = (*LayerFound)->GetRightSwapChain()->GetTextureCube();
LeftTexture = (*LayerFound)->GetSwapChain()->GetTextureCube();
}
else
{
Texture = LeftTexture = (*LayerFound)->GetSwapChain()->GetTextureCube();
}
}
else if (Desc.HasShape<FCylinderLayer>() || Desc.HasShape<FQuadLayer>())
{
if (bRightTexture)
{
Texture = (*LayerFound)->GetRightSwapChain()->GetTexture2D();
LeftTexture = (*LayerFound)->GetSwapChain()->GetTexture2D();
}
else
{
Texture = LeftTexture = (*LayerFound)->GetSwapChain()->GetTexture2D();
}
}
}
}
IStereoLayers::FLayerDesc FOculusXRHMD::GetDebugCanvasLayerDesc(FTextureRHIRef Texture)
{
IStereoLayers::FLayerDesc StereoLayerDesc;
ovrpBool cylinderSupported = ovrpBool_False;
ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().IsLayerShapeSupported(ovrpShape_Cylinder, &cylinderSupported);
if (OVRP_SUCCESS(result) && cylinderSupported)
{
StereoLayerDesc = IStereoLayers::FLayerDesc(FCylinderLayer(100.f, 488.f / 4, 180.f));
StereoLayerDesc.Transform = FTransform(FVector(0.f, 0, 0)); // 100/0/0 for quads
}
else
{
StereoLayerDesc.Transform = FTransform(FVector(100.f, 0, 0));
}
StereoLayerDesc.QuadSize = FVector2D(180.f, 180.f);
StereoLayerDesc.PositionType = IStereoLayers::ELayerType::FaceLocked;
StereoLayerDesc.LayerSize = Texture->GetTexture2D()->GetSizeXY();
StereoLayerDesc.Flags = IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_CONTINUOUS_UPDATE;
StereoLayerDesc.Flags |= IStereoLayers::ELayerFlags::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO;
return StereoLayerDesc;
}
void FOculusXRHMD::SetupViewFamily(FSceneViewFamily& InViewFamily)
{
InViewFamily.EngineShowFlags.StereoRendering = IsStereoEnabled();
}
void FOculusXRHMD::SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView)
{
CheckInGameThread();
}
void FOculusXRHMD::BeginRenderViewFamily(FSceneViewFamily& InViewFamily)
{
CheckInGameThread();
if (Settings.IsValid() && Settings->IsStereoEnabled())
{
// This should already have been set by UpdateStereoRenderingParams().
// It must still match the value used there.
check(Settings->CurrentShaderPlatform == InViewFamily.Scene->GetShaderPlatform());
Settings->Flags.bsRGBEyeBuffer = IsMobilePlatform(Settings->CurrentShaderPlatform) && IsMobileColorsRGB();
if (NextFrameToRender.IsValid())
{
NextFrameToRender->ShowFlags = InViewFamily.EngineShowFlags;
}
if (SpectatorScreenController != nullptr)
{
SpectatorScreenController->BeginRenderViewFamily();
}
}
StartRenderFrame_GameThread();
}
void FOculusXRHMD::EnableInsightPassthrough_RenderThread(bool bEnablePassthrough)
{
const bool bShouldEnable = (InsightInitStatus == FInsightInitStatus::NotInitialized) && bEnablePassthrough;
if (bShouldEnable)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().InitializeInsightPassthrough()))
{
UE_LOG(LogHMD, Log, TEXT("Passthrough Initialized"));
InsightInitStatus = FInsightInitStatus::Initialized;
}
else
{
InsightInitStatus = FInsightInitStatus::Failed;
UE_LOG(LogHMD, Log, TEXT("Passthrough initialization failed"));
}
}
else
{
const bool bShouldShutdown = (InsightInitStatus == FInsightInitStatus::Initialized) && !bEnablePassthrough;
if (bShouldShutdown)
{
// it may already be deinitialized.
if (!FOculusXRHMDModule::GetPluginWrapper().GetInsightPassthroughInitialized() || OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().ShutdownInsightPassthrough()))
{
UE_LOG(LogHMD, Log, TEXT("Passthrough shutdown"));
InsightInitStatus = FInsightInitStatus::NotInitialized;
}
else
{
UE_LOG(LogHMD, Log, TEXT("Failed to shut down passthrough. It may be still in use."));
}
}
}
}
void FOculusXRHMD::PreRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& ViewFamily)
{
CheckInRenderThread();
}
void FOculusXRHMD::OnBeginRendering_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily)
{
CheckInRenderThread();
if (!Frame_RenderThread.IsValid())
{
return;
}
if (!Settings_RenderThread.IsValid() || !Settings_RenderThread->IsStereoEnabled())
{
return;
}
// If using OVRPlugin OpenXR, only update spectator screen mode with VR focus, since we are running the frameloop
// and cycling through the swapchain even without VR focus with OVRPlugin OpenXR
ovrpXrApi NativeXrApi;
FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi);
if (SpectatorScreenController && (NativeXrApi != ovrpXrApi_OpenXR || FApp::HasVRFocus()))
{
SpectatorScreenController->UpdateSpectatorScreenMode_RenderThread();
Frame_RenderThread->Flags.bSpectatorScreenActive = SpectatorScreenController->GetSpectatorScreenMode() != ESpectatorScreenMode::Disabled;
}
// Update mirror texture
CustomPresent->UpdateMirrorTexture_RenderThread();
#if !PLATFORM_ANDROID
#if 0 // The entire target should be cleared by the tonemapper and pp material \
// Clear the padding between two eyes
const int32 GapMinX = ViewFamily.Views[0]->UnscaledViewRect.Max.X;
const int32 GapMaxX = ViewFamily.Views[1]->UnscaledViewRect.Min.X;
if (GapMinX < GapMaxX)
{
SCOPED_DRAW_EVENT(RHICmdList, OculusClearQuad)
const int32 GapMinY = ViewFamily.Views[0]->UnscaledViewRect.Min.Y;
const int32 GapMaxY = ViewFamily.Views[1]->UnscaledViewRect.Max.Y;
FRHIRenderPassInfo RPInfo(ViewFamily.RenderTarget->GetRenderTargetTexture(), ERenderTargetActions::DontLoad_Store);
RHICmdList.BeginRenderPass(RPInfo, TEXT("Clear"));
{
RHICmdList.SetViewport(GapMinX, GapMinY, 0, GapMaxX, GapMaxY, 1.0f);
DrawClearQuad(RHICmdList, FLinearColor::Black);
}
RHICmdList.EndRenderPass();
}
#endif
#else
// ensure we have attached JNI to this thread - this has to happen persistently as the JNI could detach if the app loses focus
FAndroidApplication::GetJavaEnv();
#endif
EnableInsightPassthrough_RenderThread(Settings_RenderThread->Flags.bInsightPassthroughEnabled);
// Start RHI frame
StartRHIFrame_RenderThread();
// Update performance stats
PerformanceStats.Frames++;
PerformanceStats.Seconds = FPlatformTime::Seconds();
}
void FOculusXRHMD::PreRenderView_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView)
{
}
void FOculusXRHMD::PostRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily)
{
CheckInRenderThread();
if (InViewFamily.Views[0]->StereoPass != EStereoscopicPass::eSSP_FULL)
{
FinishRenderFrame_RenderThread(GraphBuilder);
}
}
#if UE_VERSION_OLDER_THAN(5, 3, 0)
void FOculusXRHMD::PostRenderBasePassMobile_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView)
#else
void FOculusXRHMD::PostRenderBasePassMobile_RenderThread(FRHICommandList& RHICmdList, FSceneView& InView)
#endif
{
if (bHardOcclusionsEnabled)
{
RenderHardOcclusions_RenderThread(RHICmdList, InView);
}
#ifndef WITH_OCULUS_BRANCH
UpdateFoveationOffsets_RenderThread();
#endif
}
BEGIN_SHADER_PARAMETER_STRUCT(FPostBasePassViewExtensionParameters, )
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTextures)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
void FOculusXRHMD::PostRenderBasePassDeferred_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView, const FRenderTargetBindingSlots& RenderTargets, TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTextures)
{
if (bHardOcclusionsEnabled)
{
auto* PassParameters = GraphBuilder.AllocParameters<FPostBasePassViewExtensionParameters>();
PassParameters->RenderTargets = RenderTargets;
PassParameters->SceneTextures = SceneTextures;
GraphBuilder.AddPass(RDG_EVENT_NAME("RenderHardOcclusions_RenderThread"), PassParameters, ERDGPassFlags::Raster, [this, &InView](FRHICommandListImmediate& RHICmdList) {
RenderHardOcclusions_RenderThread(RHICmdList, InView);
});
}
}
#ifdef WITH_OCULUS_BRANCH
#if UE_VERSION_OLDER_THAN(5, 3, 0)
void FOculusXRHMD::PostSceneColorRenderingMobile_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView)
#else
void FOculusXRHMD::PostSceneColorRenderingMobile_RenderThread(FRHICommandList& RHICmdList, FSceneView& InView)
#endif
{
UpdateFoveationOffsets_RenderThread();
}
#endif
int32 FOculusXRHMD::GetPriority() const
{
// We want to run after the FDefaultXRCamera's view extension
return -1;
}
#ifdef WITH_OCULUS_BRANCH
bool FOculusXRHMD::LateLatchingEnabled() const
{
#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN && PLATFORM_ANDROID
// No LateLatching supported when occlusion culling is enabled due to mid frame submission
// No LateLatching supported for non Multi view ATM due to viewUniformBuffer reusing.
// The setting can be disabled in FOculusXRHMD::UpdateStereoRenderingParams
return Settings->bLateLatching;
#else
return false;
#endif
}
void FOculusXRHMD::PreLateLatchingViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily)
{
CheckInRenderThread();
FGameFrame* CurrentFrame = GetFrame_RenderThread();
if (CurrentFrame)
{
CurrentFrame->Flags.bRTLateUpdateDone = false; // Allow LateLatching to update poses again
}
}
#endif
bool FOculusXRHMD::SupportsSpaceWarp() const
{
#if PLATFORM_ANDROID
// Use All static value here since those can't be change at runtime
ensureMsgf(CustomPresent.IsValid(), TEXT("SupportsSpaceWarp can only be called post CustomPresent created"));
const bool bOvrPlugin_OpenXR = Settings->XrApi == EOculusXRXrApi::OVRPluginOpenXR;
static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView"));
static const auto CVarSupportMobileSpaceWarp = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.SupportMobileSpaceWarp"));
bool bIsMobileMultiViewEnabled = (CVarMobileMultiView && CVarMobileMultiView->GetValueOnAnyThread() != 0);
bool bIsUsingMobileMultiView = GSupportsMobileMultiView && bIsMobileMultiViewEnabled;
bool bIsVulkan = CustomPresent->GetRenderAPI() == ovrpRenderAPI_Vulkan;
bool spaceWarpSupported = bOvrPlugin_OpenXR && bIsVulkan && bIsUsingMobileMultiView && CVarSupportMobileSpaceWarp && (CVarSupportMobileSpaceWarp->GetValueOnAnyThread() != 0);
return spaceWarpSupported;
#else
return false;
#endif
}
FOculusXRHMD::FOculusXRHMD(const FAutoRegister& AutoRegister)
: FHeadMountedDisplayBase(nullptr)
, FHMDSceneViewExtension(AutoRegister)
, ConsoleCommands(this)
, InsightInitStatus(FInsightInitStatus::NotInitialized)
, bShutdownRequestQueued(false)
, bShouldWait_GameThread(true)
, bIsRendering_RenderThread(false)
{
Flags.Raw = 0;
OCFlags.Raw = 0;
TrackingOrigin = EHMDTrackingOrigin::Type::Eye;
DeltaControlRotation = FRotator::ZeroRotator; // used from ApplyHmdRotation
LastPlayerOrientation = FQuat::Identity;
LastPlayerLocation = FVector::ZeroVector;
CachedWindowSize = FIntPoint::ZeroValue;
CachedWorldToMetersScale = 100.0f;
LastTrackingToWorld = FTransform::Identity;
NextFrameNumber = 0;
WaitFrameNumber = (uint32)-1;
NextLayerId = 0;
Settings = CreateNewSettings();
RendererModule = nullptr;
SplashLayerHandle = -1;
SplashRotation = FRotator();
bIsStandaloneStereoOnlyDevice = IHeadMountedDisplayModule::IsAvailable() && IHeadMountedDisplayModule::Get().IsStandaloneStereoOnlyDevice();
bMultiPlayer = false;
}
FOculusXRHMD::~FOculusXRHMD()
{
Shutdown();
}
bool FOculusXRHMD::Startup()
{
if (GIsEditor)
{
Settings->Flags.bHeadTrackingEnforced = true;
}
check(!CustomPresent.IsValid());
FString RHIString;
{
FString HardwareDetails = FHardwareInfo::GetHardwareDetailsString();
FString RHILookup = NAME_RHI.ToString() + TEXT("=");
if (!FParse::Value(*HardwareDetails, *RHILookup, RHIString))
{
return false;
}
}
#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11
if (RHIString == TEXT("D3D11"))
{
CustomPresent = CreateCustomPresent_D3D11(this);
}
else
#endif
#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12
if (RHIString == TEXT("D3D12"))
{
CustomPresent = CreateCustomPresent_D3D12(this);
}
else
#endif
#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN
if (RHIString == TEXT("Vulkan"))
{
CustomPresent = CreateCustomPresent_Vulkan(this);
}
else
#endif
{
UE_LOG(LogHMD, Warning, TEXT("%s is not currently supported by OculusXRHMD plugin"), *RHIString);
return false;
}
// grab a pointer to the renderer module for displaying our mirror window
static const FName RendererModuleName("Renderer");
RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName);
#if PLATFORM_ANDROID
// register our application lifetime delegates
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddRaw(this, &FOculusXRHMD::ApplicationPauseDelegate);
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddRaw(this, &FOculusXRHMD::ApplicationResumeDelegate);
#endif
// Create eye layer
IStereoLayers::FLayerDesc EyeLayerDesc;
EyeLayerDesc.Priority = INT_MIN;
EyeLayerDesc.Flags = LAYER_FLAG_TEX_CONTINUOUS_UPDATE;
uint32 EyeLayerId = CreateLayer(EyeLayerDesc);
check(EyeLayerId == 0);
Splash = MakeShareable(new FSplash(this));
Splash->Startup();
#if !PLATFORM_ANDROID
SpectatorScreenController = MakeUnique<FSpectatorScreenController>(this);
#endif
UE_LOG(LogHMD, Log, TEXT("Oculus plugin initialized. Version: %s"), *GetVersionString());
return true;
}
void FOculusXRHMD::PreShutdown()
{
if (Splash.IsValid())
{
Splash->PreShutdown();
}
}
void FOculusXRHMD::Shutdown()
{
CheckInGameThread();
if (Splash.IsValid())
{
Splash->Shutdown();
Splash = nullptr;
// The base implementation stores a raw pointer to the Splash object and tries to deallocate it in its destructor
LoadingScreen = nullptr;
}
if (CustomPresent.IsValid())
{
CustomPresent->Shutdown();
CustomPresent = nullptr;
}
ReleaseDevice();
Settings.Reset();
LayerMap.Reset();
}
void FOculusXRHMD::ApplicationPauseDelegate()
{
ExecuteOnRenderThread([this]() {
ExecuteOnRHIThread([this]() {
FOculusXRHMDModule::GetPluginWrapper().DestroyDistortionWindow2();
});
});
OCFlags.AppIsPaused = true;
}
void FOculusXRHMD::ApplicationResumeDelegate()
{
if (OCFlags.AppIsPaused && !InitializeSession())
{
UE_LOG(LogHMD, Log, TEXT("HMD initialization failed"));
}
OCFlags.AppIsPaused = false;
}
static const FString EYE_TRACKING_PERMISSION_NAME("com.oculus.permission.EYE_TRACKING");
bool FOculusXRHMD::CheckEyeTrackingPermission(EOculusXRFoveatedRenderingMethod InFoveatedRenderingMethod)
{
#if PLATFORM_ANDROID
// Check and request eye tracking permissions, bind delegate for handling permission request result
if (!UAndroidPermissionFunctionLibrary::CheckPermission(EYE_TRACKING_PERMISSION_NAME))
{
TArray<FString> Permissions;
Permissions.Add(EYE_TRACKING_PERMISSION_NAME);
UAndroidPermissionCallbackProxy* Proxy = UAndroidPermissionFunctionLibrary::AcquirePermissions(Permissions);
Proxy->OnPermissionsGrantedDelegate.AddLambda([this, InFoveatedRenderingMethod](const TArray<FString>& Permissions, const TArray<bool>& GrantResults) {
int PermIndex = Permissions.Find(EYE_TRACKING_PERMISSION_NAME);
if (PermIndex != INDEX_NONE && GrantResults[PermIndex])
{
UE_LOG(LogHMD, Verbose, TEXT("com.oculus.permission.EYE_TRACKING permission granted"));
FoveatedRenderingMethod = InFoveatedRenderingMethod;
FOculusEventDelegates::OculusEyeTrackingStateChanged.Broadcast(true);
}
else
{
UE_LOG(LogHMD, Log, TEXT("com.oculus.permission.EYE_TRACKING permission denied"));
if (InFoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering)
{
EyeTrackedFoveatedRenderingFallback();
}
FOculusEventDelegates::OculusEyeTrackingStateChanged.Broadcast(false);
}
});
return false;
}
#endif // PLATFORM_ANDROID
return true;
}
bool FOculusXRHMD::InitializeSession()
{
UE_LOG(LogHMD, Log, TEXT("Initializing OVRPlugin session"));
if (!FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
#if !UE_BUILD_SHIPPING
ovrpLogCallback logCallback = OvrpLogCallback;
#else
ovrpLogCallback logCallback = nullptr;
#endif
#if PLATFORM_ANDROID
void* activity = (void*)FAndroidApplication::GetGameActivityThis();
#else
void* activity = nullptr;
#endif
int initializeFlags = GIsEditor ? ovrpInitializeFlag_SupportsVRToggle : 0;
initializeFlags |= CustomPresent->SupportsSRGB() ? ovrpInitializeFlag_SupportSRGBFrameBuffer : 0;
if (Settings->Flags.bSupportsDash)
{
initializeFlags |= ovrpInitializeFlag_FocusAware;
}
if (SupportsSpaceWarp()) // Configure for space warp
{
initializeFlags |= ovrpInitializeFlag_SupportAppSpaceWarp;
UE_LOG(LogHMD, Log, TEXT("[Mobile SpaceWarp] Application is configured to support mobile spacewarp"));
}
bNeedReAllocateMotionVectorTexture_RenderThread = false;
#if WITH_EDITOR && PLATFORM_WINDOWS
// Attempt Late Initialization in-editor
// FOculusXRHMDModule::PreInit always returns true in this case,
// so we need to check the flag directly.
if (GIsEditor && FOculusXRHMDModule::Get().PreInit() && !FOculusXRHMDModule::Get().bPreInit)
{
return false;
}
#endif
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().Initialize7(
CustomPresent->GetRenderAPI(),
logCallback,
activity,
CustomPresent->GetOvrpInstance(),
CustomPresent->GetOvrpPhysicalDevice(),
CustomPresent->GetOvrpDevice(),
CustomPresent->GetOvrpCommandQueue(),
nullptr /*vkGetInstanceProcAddr*/,
0 /*vkQueueFamilyIndex*/,
nullptr /*d3dDevice*/,
initializeFlags,
{ OVRP_VERSION })))
{
return false;
}
ovrpBool Supported = ovrpBool_False;
if (Settings->bSupportEyeTrackedFoveatedRendering)
{
FOculusXRHMDModule::GetPluginWrapper().GetFoveationEyeTrackedSupported(&Supported);
}
bEyeTrackedFoveatedRenderingSupported = Supported == ovrpBool_True;
SetFoveatedRenderingMethod(Settings->FoveatedRenderingMethod);
SetFoveatedRenderingLevel(Settings->FoveatedRenderingLevel, Settings->bDynamicFoveatedRendering);
NextFrameNumber = 0;
WaitFrameNumber = (uint32)-1;
}
FOculusXRHMDModule::GetPluginWrapper().SetAppEngineInfo2(
"Unreal Engine",
TCHAR_TO_ANSI(*FEngineVersion::Current().ToString()),
GIsEditor ? ovrpBool_True : ovrpBool_False);
int flag = ovrpDistortionWindowFlag_None;
FOculusXRHMDModule::GetPluginWrapper().SetupDistortionWindow3(flag);
FOculusXRHMDModule::GetPluginWrapper().SetSuggestedCpuPerformanceLevel((ovrpProcessorPerformanceLevel)Settings->SuggestedCpuPerfLevel);
FOculusXRHMDModule::GetPluginWrapper().SetSuggestedGpuPerformanceLevel((ovrpProcessorPerformanceLevel)Settings->SuggestedGpuPerfLevel);
FOculusXRHMDModule::GetPluginWrapper().SetFoveationEyeTracked(FoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering);
FOculusXRHMDModule::GetPluginWrapper().SetTiledMultiResLevel((ovrpTiledMultiResLevel)FoveatedRenderingLevel.load());
FOculusXRHMDModule::GetPluginWrapper().SetTiledMultiResDynamic(bDynamicFoveatedRendering.load());
FOculusXRHMDModule::GetPluginWrapper().SetAppCPUPriority2(ovrpBool_True);
FOculusXRHMDModule::GetPluginWrapper().SetLocalDimming(ovrpBool_True);
OCFlags.NeedSetTrackingOrigin = true;
FOculusXRHMDModule::GetPluginWrapper().SetClientColorDesc((ovrpColorSpace)Settings->ColorSpace);
return true;
}
void FOculusXRHMD::ShutdownSession()
{
ExecuteOnRenderThread([this]() {
ExecuteOnRHIThread([this]() {
FOculusXRHMDModule::GetPluginWrapper().DestroyDistortionWindow2();
});
});
FOculusXRHMDModule::GetPluginWrapper().Shutdown2();
bIsRendering_RenderThread = false;
}
bool FOculusXRHMD::InitDevice()
{
CheckInGameThread();
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
// Already created and present
return true;
}
if (!IsHMDEnabled())
{
// Don't bother if HMD is not connected
return false;
}
LoadFromSettings();
if (!InitializeSession())
{
UE_LOG(LogHMD, Log, TEXT("HMD initialization failed"));
return false;
}
// Don't need to reset these flags on application resume, so put them in InitDevice instead of InitializeSession
bNeedReAllocateViewportRenderTarget = true;
bNeedReAllocateDepthTexture_RenderThread = false;
bNeedReAllocateFoveationTexture_RenderThread = false;
Flags.bNeedDisableStereo = false;
OCFlags.NeedSetFocusToGameViewport = true;
if (!CustomPresent->IsUsingCorrectDisplayAdapter())
{
UE_LOG(LogHMD, Error, TEXT("Using incorrect display adapter for HMD."));
ShutdownSession();
return false;
}
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetSystemHeadsetType2(&Settings->SystemHeadset)))
{
Settings->SystemHeadset = ovrpSystemHeadset_None;
}
FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, 0, 0.0);
if (Settings->Flags.bPixelDensityAdaptive)
{
static const auto DynamicResOperationCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DynamicRes.OperationMode"));
if (DynamicResOperationCVar)
{
DynamicResOperationCVar->Set(2);
}
GEngine->ChangeDynamicResolutionStateAtNextFrame(MakeShareable(new FDynamicResolutionState(Settings)));
}
UpdateHmdRenderInfo();
UpdateStereoRenderingParams();
const bool bEnablePassthrough = Settings->Flags.bInsightPassthroughEnabled;
ExecuteOnRenderThread([this, bEnablePassthrough](FRHICommandListImmediate& RHICmdList) {
InitializeEyeLayer_RenderThread(RHICmdList);
EnableInsightPassthrough_RenderThread(bEnablePassthrough);
});
if (!EyeLayer_RenderThread.IsValid() || !EyeLayer_RenderThread->GetSwapChain().IsValid())
{
UE_LOG(LogHMD, Error, TEXT("Failed to create eye layer swap chain."));
ShutdownSession();
return false;
}
if (!HiddenAreaMeshes[0].IsValid() || !HiddenAreaMeshes[1].IsValid())
{
SetupOcclusionMeshes();
}
#if !UE_BUILD_SHIPPING
DrawDebugDelegateHandle = UDebugDrawService::Register(TEXT("Game"), FDebugDrawDelegate::CreateRaw(this, &FOculusXRHMD::DrawDebug));
#endif
// Do not set VR focus in Editor by just creating a device; Editor may have it created w/o requiring focus.
// Instead, set VR focus in OnBeginPlay (VR Preview will run there first).
if (!GIsEditor)
{
FApp::SetUseVRFocus(true);
FApp::SetHasVRFocus(true);
}
FOculusXRHMDModule::GetPluginWrapper().SetClientColorDesc((ovrpColorSpace)Settings->ColorSpace);
return true;
}
void FOculusXRHMD::ReleaseDevice()
{
CheckInGameThread();
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
// Wait until the next frame before ending the session (workaround for DX12/Vulkan resources being ripped out from under us before we're done with them).
bShutdownRequestQueued = true;
}
}
void BuildOcclusionMesh(FRHICommandList& RHICmdList, FHMDViewMesh& Mesh, ovrpEye Eye, ovrpViewportStencilType MeshType)
{
int VertexCount = 0;
int IndexCount = 0;
ovrpResult Result = ovrpResult::ovrpFailure;
if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().GetViewportStencil(Eye, MeshType, nullptr, &VertexCount, nullptr, &IndexCount)))
{
return;
}
FRHIResourceCreateInfo CreateInfo(TEXT("FOculusXRHMD"));
#if UE_VERSION_OLDER_THAN(5, 3, 0)
Mesh.VertexBufferRHI = RHICreateVertexBuffer(sizeof(FFilterVertex) * VertexCount, BUF_Static, CreateInfo);
void* VoidPtr = RHILockBuffer(Mesh.VertexBufferRHI, 0, sizeof(FFilterVertex) * VertexCount, RLM_WriteOnly);
#else
Mesh.VertexBufferRHI = RHICmdList.CreateVertexBuffer(sizeof(FFilterVertex) * VertexCount, BUF_Static, CreateInfo);
void* VoidPtr = RHICmdList.LockBuffer(Mesh.VertexBufferRHI, 0, sizeof(FFilterVertex) * VertexCount, RLM_WriteOnly);
#endif
FFilterVertex* pVertices = reinterpret_cast<FFilterVertex*>(VoidPtr);
#if UE_VERSION_OLDER_THAN(5, 3, 0)
Mesh.IndexBufferRHI = RHICreateIndexBuffer(sizeof(uint16), sizeof(uint16) * IndexCount, BUF_Static, CreateInfo);
void* VoidPtr2 = RHILockBuffer(Mesh.IndexBufferRHI, 0, sizeof(uint16) * IndexCount, RLM_WriteOnly);
#else
Mesh.IndexBufferRHI = RHICmdList.CreateIndexBuffer(sizeof(uint16), sizeof(uint16) * IndexCount, BUF_Static, CreateInfo);
void* VoidPtr2 = RHICmdList.LockBuffer(Mesh.IndexBufferRHI, 0, sizeof(uint16) * IndexCount, RLM_WriteOnly);
#endif
uint16* pIndices = reinterpret_cast<uint16*>(VoidPtr2);
ovrpVector2f* const ovrpVertices = new ovrpVector2f[VertexCount];
FOculusXRHMDModule::GetPluginWrapper().GetViewportStencil(Eye, MeshType, ovrpVertices, &VertexCount, pIndices, &IndexCount);
for (int i = 0; i < VertexCount; ++i)
{
FFilterVertex& Vertex = pVertices[i];
CA_SUPPRESS(6385); // warning C6385: Reading invalid data from 'ovrpVertices': the readable size is 'VertexCount*8' bytes, but '16' bytes may be read
const ovrpVector2f& Position = ovrpVertices[i];
if (MeshType == ovrpViewportStencilType_HiddenArea)
{
Vertex.Position.X = (Position.x * 2.0f) - 1.0f;
Vertex.Position.Y = (Position.y * 2.0f) - 1.0f;
Vertex.Position.Z = 1.0f;
Vertex.Position.W = 1.0f;
Vertex.UV.X = 0.0f;
Vertex.UV.Y = 0.0f;
}
else if (MeshType == ovrpViewportStencilType_VisibleArea)
{
Vertex.Position.X = Position.x;
Vertex.Position.Y = 1.0f - Position.y;
Vertex.Position.Z = 0.0f;
Vertex.Position.W = 1.0f;
Vertex.UV.X = Position.x;
Vertex.UV.Y = 1.0f - Position.y;
}
else
{
check(0);
}
}
Mesh.NumIndices = IndexCount;
Mesh.NumVertices = VertexCount;
Mesh.NumTriangles = IndexCount / 3;
delete[] ovrpVertices;
#if UE_VERSION_OLDER_THAN(5, 3, 0)
RHIUnlockBuffer(Mesh.VertexBufferRHI);
RHIUnlockBuffer(Mesh.IndexBufferRHI);
#else
RHICmdList.UnlockBuffer(Mesh.VertexBufferRHI);
RHICmdList.UnlockBuffer(Mesh.IndexBufferRHI);
#endif
}
void FOculusXRHMD::SetupOcclusionMeshes()
{
CheckInGameThread();
FOculusXRHMD* const Self = this;
ENQUEUE_RENDER_COMMAND(SetupOcclusionMeshesCmd)
([Self](FRHICommandList& RHICmdList) {
BuildOcclusionMesh(RHICmdList, Self->HiddenAreaMeshes[0], ovrpEye_Left, ovrpViewportStencilType_HiddenArea);
BuildOcclusionMesh(RHICmdList, Self->HiddenAreaMeshes[1], ovrpEye_Right, ovrpViewportStencilType_HiddenArea);
BuildOcclusionMesh(RHICmdList, Self->VisibleAreaMeshes[0], ovrpEye_Left, ovrpViewportStencilType_VisibleArea);
BuildOcclusionMesh(RHICmdList, Self->VisibleAreaMeshes[1], ovrpEye_Right, ovrpViewportStencilType_VisibleArea);
});
}
static ovrpMatrix4f ovrpMatrix4f_Projection(const ovrpFrustum2f& frustum, bool leftHanded)
{
float handednessScale = leftHanded ? 1.0f : -1.0f;
// A projection matrix is very like a scaling from NDC, so we can start with that.
float projXScale = 2.0f / (frustum.Fov.LeftTan + frustum.Fov.RightTan);
float projXOffset = (frustum.Fov.LeftTan - frustum.Fov.RightTan) * projXScale * 0.5f;
float projYScale = 2.0f / (frustum.Fov.UpTan + frustum.Fov.DownTan);
float projYOffset = (frustum.Fov.UpTan - frustum.Fov.DownTan) * projYScale * 0.5f;
ovrpMatrix4f projection;
// Produces X result, mapping clip edges to [-w,+w]
projection.M[0][0] = projXScale;
projection.M[0][1] = 0.0f;
projection.M[0][2] = handednessScale * projXOffset;
projection.M[0][3] = 0.0f;
// Produces Y result, mapping clip edges to [-w,+w]
// Hey - why is that YOffset negated?
// It's because a projection matrix transforms from world coords with Y=up,
// whereas this is derived from an NDC scaling, which is Y=down.
projection.M[1][0] = 0.0f;
projection.M[1][1] = projYScale;
projection.M[1][2] = handednessScale * -projYOffset;
projection.M[1][3] = 0.0f;
// Produces Z-buffer result
projection.M[2][0] = 0.0f;
projection.M[2][1] = 0.0f;
if (FGenericPlatformMath::IsFinite(frustum.zFar))
{
projection.M[2][2] = -handednessScale * frustum.zFar / (frustum.zNear - frustum.zFar);
projection.M[2][3] = (frustum.zFar * frustum.zNear) / (frustum.zNear - frustum.zFar);
}
else
{
projection.M[2][2] = handednessScale;
projection.M[2][3] = -frustum.zNear;
}
// Produces W result (= Z in)
projection.M[3][0] = 0.0f;
projection.M[3][1] = 0.0f;
projection.M[3][2] = handednessScale;
projection.M[3][3] = 0.0f;
return projection;
}
void FOculusXRHMD::UpdateStereoRenderingParams()
{
CheckInGameThread();
// Update PixelDensity
bool bSupportsDepth = true;
if (Settings->Flags.bPixelDensityAdaptive)
{
FLayer* EyeLayer = EyeLayer_RenderThread.Get();
float NewPixelDensity = 1.0;
if (EyeLayer && EyeLayer->GetOvrpId())
{
ovrpSizei RecommendedResolution = { 0, 0 };
FOculusXRHMDModule::GetPluginWrapper().GetLayerRecommendedResolution(EyeLayer->GetOvrpId(), &RecommendedResolution);
if (RecommendedResolution.h > 0)
{
NewPixelDensity = RecommendedResolution.h * (float)Settings->PixelDensityMax / Settings->RenderTargetSize.Y;
}
}
const float PixelDensityCVarOverride = CVarOculusDynamicResolutionPixelDensity.GetValueOnAnyThread();
if (PixelDensityCVarOverride > 0)
{
NewPixelDensity = PixelDensityCVarOverride;
}
Settings->SetPixelDensitySmooth(NewPixelDensity);
}
else
{
static const auto PixelDensityCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("vr.PixelDensity"));
Settings->SetPixelDensity(PixelDensityCVar ? PixelDensityCVar->GetFloat() : 1.0f);
// Due to hijacking the depth target directly from the scene context, we can't support depth compositing if it's being scaled by screen percentage since it wont match our color render target dimensions.
static const auto ScreenPercentageCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ScreenPercentage"));
float ScreenPercentage = (!ScreenPercentageCVar) ? 100.0f : ScreenPercentageCVar->GetFloat();
#if !UE_VERSION_OLDER_THAN(5, 3, 0)
// 5.3 changes the default screen percentage to 0 and uses r.ScreenPercentage.Default values to determine screen percentage
if (ScreenPercentageCVar->GetFloat() <= 0.0f)
{
static const auto VRScreenPercentageModeCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ScreenPercentage.Default.VR.Mode"));
// With default VR screen percentage modes, only support depth with manual screen percentage set to 100
if (VRScreenPercentageModeCVar && VRScreenPercentageModeCVar->GetInt() == static_cast<int>(EScreenPercentageMode::Manual))
{
static const auto ManualScreenPercentageCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ScreenPercentage.Default"));
ScreenPercentage = ManualScreenPercentageCVar->GetFloat();
}
}
#endif
bSupportsDepth = ScreenPercentage == 100.0f;
}
// Update EyeLayer
FLayerPtr* EyeLayerFound = LayerMap.Find(0);
FLayer* EyeLayer = new FLayer(**EyeLayerFound);
*EyeLayerFound = MakeShareable(EyeLayer);
ovrpLayout Layout = ovrpLayout_DoubleWide;
static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView"));
const bool bIsMobileMultiViewEnabled = (CVarMobileMultiView && CVarMobileMultiView->GetValueOnAnyThread() != 0);
const bool bIsUsingMobileMultiView = (GSupportsMobileMultiView || GRHISupportsArrayIndexFromAnyShader) && bIsMobileMultiViewEnabled;
Settings->CurrentFeatureLevel = GEngine ? GEngine->GetDefaultWorldFeatureLevel() : GMaxRHIFeatureLevel;
Settings->CurrentShaderPlatform = GShaderPlatformForFeatureLevel[Settings->CurrentFeatureLevel];
// for now only mobile rendering codepaths use the array rendering system, so PC-native should stay in doublewide
if (bIsUsingMobileMultiView && IsMobilePlatform(Settings->CurrentShaderPlatform))
{
Layout = ovrpLayout_Array;
}
#if PLATFORM_ANDROID
if (!bIsUsingMobileMultiView && Settings->bLateLatching)
{
UE_CLOG(true, LogHMD, Error, TEXT("LateLatching can't be used when Multiview is off, force disabling."));
Settings->bLateLatching = false;
}
if (GetMutableDefault<URendererSettings>()->bOcclusionCulling && Settings->bLateLatching)
{
UE_CLOG(true, LogHMD, Error, TEXT("LateLatching can't used when Occlusion culling is on due to mid frame vkQueueSubmit, force disabling"));
Settings->bLateLatching = false;
}
#endif
const bool bForceSymmetric = CVarOculusForceSymmetric.GetValueOnAnyThread() == 1 && (Layout == ovrpLayout_Array);
ovrpLayerDesc_EyeFov EyeLayerDesc;
const bool requestsSubsampled = CVarOculusEnableSubsampledLayout.GetValueOnAnyThread() == 1 && CustomPresent->SupportsSubsampled();
int eyeLayerFlags = requestsSubsampled ? ovrpLayerFlag_Subsampled : 0;
ovrpTextureFormat MvPixelFormat = ovrpTextureFormat_R16G16B16A16_FP;
ovrpTextureFormat MvDepthFormat = ovrpTextureFormat_D24_S8;
int SpaceWarpAllocateFlag = 0;
if (SupportsSpaceWarp())
{
SpaceWarpAllocateFlag = ovrpLayerFlag_SpaceWarpDataAllocation | ovrpLayerFlag_SpaceWarpDedicatedDepth;
bool spaceWarpEnabledByUser = CVarOculusEnableSpaceWarpUser.GetValueOnAnyThread() != 0;
bool spaceWarpEnabledInternal = CVarOculusEnableSpaceWarpInternal.GetValueOnAnyThread() != 0;
if (spaceWarpEnabledByUser != spaceWarpEnabledInternal)
{
CVarOculusEnableSpaceWarpInternal->Set(spaceWarpEnabledByUser);
}
}
const bool bCompositeDepth = Settings->Flags.bCompositeDepth;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().CalculateEyeLayerDesc3(
Layout,
Settings->Flags.bPixelDensityAdaptive ? Settings->PixelDensityMax : Settings->PixelDensity,
Settings->Flags.bHQDistortion ? 0 : 1,
1, // UNDONE
CustomPresent->GetOvrpTextureFormat(CustomPresent->GetDefaultPixelFormat(), Settings->Flags.bsRGBEyeBuffer),
(bCompositeDepth && bSupportsDepth) ? CustomPresent->GetDefaultDepthOvrpTextureFormat() : ovrpTextureFormat_None,
MvPixelFormat,
MvDepthFormat,
1.0f,
CustomPresent->GetLayerFlags() | eyeLayerFlags | SpaceWarpAllocateFlag,
&EyeLayerDesc)))
{
ovrpFovf FrameFov[ovrpEye_Count] = { EyeLayerDesc.Fov[0], EyeLayerDesc.Fov[1] };
if (bForceSymmetric)
{
// calculate symmetric FOV from runtime-provided asym in EyeLayerDesc
FrameFov[0].RightTan = FrameFov[1].RightTan = FMath::Max(EyeLayerDesc.Fov[0].RightTan, EyeLayerDesc.Fov[1].RightTan);
FrameFov[0].LeftTan = FrameFov[1].LeftTan = FMath::Max(EyeLayerDesc.Fov[0].LeftTan, EyeLayerDesc.Fov[1].LeftTan);
const float asymTanSize = EyeLayerDesc.Fov[0].RightTan + EyeLayerDesc.Fov[0].LeftTan;
const float symTanSize = FrameFov[0].RightTan + FrameFov[0].LeftTan;
// compute new resolution from a number of tile multiple, and the increase in width from symmetric FOV
const int numberTiles = (int)floor(EyeLayerDesc.TextureSize.w * symTanSize / (96.0 * asymTanSize));
EyeLayerDesc.TextureSize.w = EyeLayerDesc.MaxViewportSize.w = numberTiles * 96;
}
// Scaling for DynamicResolution will happen later - see FSceneRenderer::PrepareViewRectsForRendering.
// If scaling does occur, EyeRenderViewport will be updated in FOculusXRHMD::SetFinalViewRect.
FIntPoint UnscaledViewportSize = FIntPoint(EyeLayerDesc.MaxViewportSize.w, EyeLayerDesc.MaxViewportSize.h);
if (Settings->Flags.bPixelDensityAdaptive)
{
FIntPoint ViewRect = FIntPoint(
FMath::CeilToInt(EyeLayerDesc.MaxViewportSize.w / Settings->PixelDensityMax),
FMath::CeilToInt(EyeLayerDesc.MaxViewportSize.h / Settings->PixelDensityMax));
UnscaledViewportSize = ViewRect;
FIntPoint UpperViewRect = FIntPoint(
FMath::CeilToInt(ViewRect.X * Settings->PixelDensityMax),
FMath::CeilToInt(ViewRect.Y * Settings->PixelDensityMax));
FIntPoint TextureSize;
QuantizeSceneBufferSize(UpperViewRect, TextureSize);
EyeLayerDesc.MaxViewportSize.w = TextureSize.X;
EyeLayerDesc.MaxViewportSize.h = TextureSize.Y;
}
// Unreal assumes no gutter between eyes
EyeLayerDesc.TextureSize.w = EyeLayerDesc.MaxViewportSize.w;
EyeLayerDesc.TextureSize.h = EyeLayerDesc.MaxViewportSize.h;
if (Layout == ovrpLayout_DoubleWide)
{
EyeLayerDesc.TextureSize.w *= 2;
}
EyeLayer->SetEyeLayerDesc(EyeLayerDesc);
EyeLayer->bNeedsTexSrgbCreate = Settings->Flags.bsRGBEyeBuffer;
Settings->RenderTargetSize = FIntPoint(EyeLayerDesc.TextureSize.w, EyeLayerDesc.TextureSize.h);
Settings->EyeRenderViewport[0].Min = FIntPoint::ZeroValue;
Settings->EyeRenderViewport[0].Max = UnscaledViewportSize;
Settings->EyeRenderViewport[1].Min = FIntPoint(Layout == ovrpLayout_DoubleWide ? UnscaledViewportSize.X : 0, 0);
Settings->EyeRenderViewport[1].Max = Settings->EyeRenderViewport[1].Min + UnscaledViewportSize;
Settings->EyeUnscaledRenderViewport[0] = Settings->EyeRenderViewport[0];
Settings->EyeUnscaledRenderViewport[1] = Settings->EyeRenderViewport[1];
// Update projection matrices
ovrpFrustum2f frustumLeft = { 0.001f, 1000.0f, FrameFov[0] };
ovrpFrustum2f frustumRight = { 0.001f, 1000.0f, FrameFov[1] };
ovrpFrustum2f frustumCenter = { 0.001f, 1000.0f, { FrameFov[0].UpTan, FrameFov[0].DownTan, FrameFov[0].LeftTan, FrameFov[1].RightTan } };
Settings->EyeProjectionMatrices[0] = ovrpMatrix4f_Projection(frustumLeft, true);
Settings->EyeProjectionMatrices[1] = ovrpMatrix4f_Projection(frustumRight, true);
Settings->MonoProjectionMatrix = ovrpMatrix4f_Projection(frustumCenter, true);
// given that we send a subrect in vpRectSubmit, the FOV is the default asym one in EyeLayerDesc, not FrameFov
if (Frame.IsValid())
{
Frame->Fov[0] = EyeLayerDesc.Fov[0];
Frame->Fov[1] = EyeLayerDesc.Fov[1];
Frame->SymmetricFov[0] = FrameFov[0];
Frame->SymmetricFov[1] = FrameFov[1];
}
// Flag if need to recreate render targets
if (!EyeLayer->CanReuseResources(EyeLayer_RenderThread.Get()))
{
AllocateEyeBuffer();
}
}
}
void FOculusXRHMD::UpdateHmdRenderInfo()
{
CheckInGameThread();
FOculusXRHMDModule::GetPluginWrapper().GetSystemDisplayFrequency2(&Settings->VsyncToNextVsync);
}
void FOculusXRHMD::InitializeEyeLayer_RenderThread(FRHICommandListImmediate& RHICmdList)
{
check(!InGameThread());
CheckInRenderThread();
if (LayerMap[0].IsValid())
{
FLayerPtr EyeLayer = LayerMap[0]->Clone();
EyeLayer->Initialize_RenderThread(Settings_RenderThread.Get(), CustomPresent, &DeferredDeletion, RHICmdList, EyeLayer_RenderThread.Get());
if (Layers_RenderThread.Num() > 0)
{
Layers_RenderThread[0] = EyeLayer;
}
else
{
Layers_RenderThread.Add(EyeLayer);
}
if (EyeLayer->GetDepthSwapChain().IsValid())
{
if (!EyeLayer_RenderThread.IsValid() || EyeLayer->GetDepthSwapChain() != EyeLayer_RenderThread->GetDepthSwapChain())
{
bNeedReAllocateDepthTexture_RenderThread = true;
}
}
if (EyeLayer->GetFoveationSwapChain().IsValid())
{
if (!EyeLayer_RenderThread.IsValid() || EyeLayer->GetFoveationSwapChain() != EyeLayer_RenderThread->GetFoveationSwapChain())
{
bNeedReAllocateFoveationTexture_RenderThread = true;
}
#if !UE_VERSION_OLDER_THAN(5, 3, 0)
FoveationImageGenerator = MakeShared<FOculusXRFoveatedRenderingImageGenerator, ESPMode::ThreadSafe>(EyeLayer->GetFoveationSwapChain());
#endif // !UE_VERSION_OLDER_THAN(5, 3, 0)
}
if (EyeLayer->GetMotionVectorSwapChain().IsValid())
{
if (!EyeLayer_RenderThread.IsValid() || EyeLayer->GetMotionVectorSwapChain() != EyeLayer_RenderThread->GetMotionVectorSwapChain()
|| EyeLayer->GetMotionVectorDepthSwapChain() != EyeLayer_RenderThread->GetMotionVectorDepthSwapChain())
{
bNeedReAllocateMotionVectorTexture_RenderThread = true;
UE_LOG(LogHMD, VeryVerbose, TEXT("[Mobile SpaceWarp] request to re-allocate motionVector textures"));
}
}
if (EyeLayer_RenderThread.IsValid())
{
DeferredDeletion.AddLayerToDeferredDeletionQueue(EyeLayer_RenderThread);
}
EyeLayer_RenderThread = EyeLayer;
}
}
void FOculusXRHMD::ApplySystemOverridesOnStereo(bool force)
{
CheckInGameThread();
// ALWAYS SET r.FinishCurrentFrame to 0! Otherwise the perf might be poor.
// @TODO: revise the FD3D11DynamicRHI::RHIEndDrawingViewport code (and other renderers)
// to ignore this var completely.
static const auto CFinishFrameVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.FinishCurrentFrame"));
CFinishFrameVar->Set(0);
}
bool FOculusXRHMD::OnOculusStateChange(bool bIsEnabledNow)
{
if (!bIsEnabledNow)
{
// Switching from stereo
ReleaseDevice();
ResetControlRotation();
return true;
}
else
{
// Switching to stereo
if (InitDevice())
{
Flags.bApplySystemOverridesOnStereo = true;
return true;
}
DeltaControlRotation = FRotator::ZeroRotator;
}
return false;
}
class FSceneViewport* FOculusXRHMD::FindSceneViewport()
{
if (!GIsEditor)
{
UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
return GameEngine->SceneViewport.Get();
}
#if WITH_EDITOR
else
{
UEditorEngine* EditorEngine = CastChecked<UEditorEngine>(GEngine);
FSceneViewport* PIEViewport = (FSceneViewport*)EditorEngine->GetPIEViewport();
if (PIEViewport != nullptr && PIEViewport->IsStereoRenderingAllowed())
{
// PIE is setup for stereo rendering
return PIEViewport;
}
else
{
// Check to see if the active editor viewport is drawing in stereo mode
// @todo vreditor: Should work with even non-active viewport!
FSceneViewport* EditorViewport = (FSceneViewport*)EditorEngine->GetActiveViewport();
if (EditorViewport != nullptr && EditorViewport->IsStereoRenderingAllowed())
{
return EditorViewport;
}
}
}
#endif
return nullptr;
}
bool FOculusXRHMD::ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread() const
{
CheckInRenderThread();
// If you really need the eye corners to look nice, and can't just crop more,
// and are willing to suffer a frametime hit... you could do this:
#if 0
switch(GetSpectatorScreenMode_RenderThread())
{
case ESpectatorScreenMode::SingleEyeLetterboxed:
case ESpectatorScreenMode::SingleEyeCroppedToFill:
case ESpectatorScreenMode::TexturePlusEye:
return true;
}
#endif
return false;
}
ESpectatorScreenMode FOculusXRHMD::GetSpectatorScreenMode_RenderThread() const
{
CheckInRenderThread();
return SpectatorScreenController ? SpectatorScreenController->GetSpectatorScreenMode() : ESpectatorScreenMode::Disabled;
}
#if !UE_BUILD_SHIPPING
static const char* FormatLatencyReading(char* buff, size_t size, float val)
{
if (val < 0.000001f)
{
FCStringAnsi::Strcpy(buff, size, "N/A ");
}
else
{
FCStringAnsi::Snprintf(buff, size, "%4.2fms", val * 1000.0f);
}
return buff;
}
void FOculusXRHMD::DrawDebug(UCanvas* InCanvas, APlayerController* InPlayerController)
{
CheckInGameThread();
if (InCanvas && IsStereoEnabled() && Settings->Flags.bShowStats)
{
static const FColor TextColor(0, 255, 0);
// Pick a larger font on console.
UFont* const Font = FPlatformProperties::SupportsWindowedMode() ? GEngine->GetSmallFont() : GEngine->GetMediumFont();
const int32 RowHeight = FMath::TruncToInt(Font->GetMaxCharHeight() * 1.1f);
float ClipX = InCanvas->ClipX;
float ClipY = InCanvas->ClipY;
float LeftPos = 0;
ClipX -= 100;
LeftPos = ClipX * 0.3f;
float TopPos = ClipY * 0.4f;
int32 X = (int32)LeftPos;
int32 Y = (int32)TopPos;
FString Str;
if (!Settings->Flags.bPixelDensityAdaptive)
{
Str = FString::Printf(TEXT("PD: %.2f"), Settings->PixelDensity);
}
else
{
Str = FString::Printf(TEXT("PD: %.2f [%0.2f, %0.2f]"), Settings->PixelDensity,
Settings->PixelDensityMin, Settings->PixelDensityMax);
}
InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor);
Y += RowHeight;
Str = FString::Printf(TEXT("W-to-m scale: %.2f uu/m"), GetWorldToMetersScale());
InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor);
ovrpAppLatencyTimings AppLatencyTimings;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppLatencyTimings2(&AppLatencyTimings)))
{
Y += RowHeight;
char buf[5][20];
char destStr[100];
FCStringAnsi::Snprintf(destStr, sizeof(destStr), "Latency, ren: %s tw: %s pp: %s err: %s %s",
FormatLatencyReading(buf[0], sizeof(buf[0]), AppLatencyTimings.LatencyRender),
FormatLatencyReading(buf[1], sizeof(buf[1]), AppLatencyTimings.LatencyTimewarp),
FormatLatencyReading(buf[2], sizeof(buf[2]), AppLatencyTimings.LatencyPostPresent),
FormatLatencyReading(buf[3], sizeof(buf[3]), AppLatencyTimings.ErrorRender),
FormatLatencyReading(buf[4], sizeof(buf[4]), AppLatencyTimings.ErrorTimewarp));
Str = ANSI_TO_TCHAR(destStr);
InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor);
}
// Second row
X = (int32)LeftPos + 200;
Y = (int32)TopPos;
Str = FString::Printf(TEXT("HQ dist: %s"), (Settings->Flags.bHQDistortion) ? TEXT("ON") : TEXT("OFF"));
InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor);
Y += RowHeight;
float UserIPD;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserIPD2(&UserIPD)))
{
Str = FString::Printf(TEXT("IPD: %.2f mm"), UserIPD * 1000.f);
InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor);
Y += RowHeight;
}
}
}
#endif // #if !UE_BUILD_SHIPPING
FOculusXRHMD* FOculusXRHMD::GetOculusXRHMD()
{
#if OCULUS_HMD_SUPPORTED_PLATFORMS
if (GEngine && GEngine->XRSystem.IsValid())
{
if (GEngine->XRSystem->GetSystemName() == OculusXRHMD::FOculusXRHMD::OculusSystemName)
{
return static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem.Get());
}
}
#endif
return nullptr;
}
bool FOculusXRHMD::IsHMDActive() const
{
return FOculusXRHMDModule::GetPluginWrapper().GetInitialized() != ovrpBool_False;
}
float FOculusXRHMD::GetWorldToMetersScale() const
{
CheckInGameThread();
if (NextFrameToRender.IsValid())
{
return NextFrameToRender->WorldToMetersScale;
}
if (GWorld != nullptr)
{
#if WITH_EDITOR
// Workaround to allow WorldToMeters scaling to work correctly for controllers while running inside PIE.
// The main world will most likely not be pointing at the PIE world while polling input, so if we find a world context
// of that type, use that world's WorldToMeters instead.
if (GIsEditor)
{
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
if (Context.WorldType == EWorldType::PIE)
{
return Context.World()->GetWorldSettings()->WorldToMeters;
}
}
}
#endif //WITH_EDITOR
// We're not currently rendering a frame, so just use whatever world to meters the main world is using.
// This can happen when we're polling input in the main engine loop, before ticking any worlds.
return GWorld->GetWorldSettings()->WorldToMeters;
}
return 100.0f;
}
FVector FOculusXRHMD::GetNeckPosition(const FQuat& HeadOrientation, const FVector& HeadPosition)
{
CheckInGameThread();
FVector NeckPosition = HeadOrientation.Inverse().RotateVector(HeadPosition);
ovrpVector2f NeckEyeDistance;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserNeckEyeDistance2(&NeckEyeDistance)))
{
const float WorldToMetersScale = GetWorldToMetersScale();
NeckPosition.X -= NeckEyeDistance.x * WorldToMetersScale;
NeckPosition.Z -= NeckEyeDistance.y * WorldToMetersScale;
}
return NeckPosition;
}
void FOculusXRHMD::SetBaseOffsetInMeters(const FVector& BaseOffset)
{
CheckInGameThread();
Settings->BaseOffset = BaseOffset;
}
FVector FOculusXRHMD::GetBaseOffsetInMeters() const
{
CheckInGameThread();
return Settings->BaseOffset;
}
bool FOculusXRHMD::ConvertPose(const ovrpPosef& InPose, FPose& OutPose) const
{
CheckInGameThread();
if (!NextFrameToRender.IsValid())
{
return false;
}
return ConvertPose_Internal(InPose, OutPose, Settings.Get(), NextFrameToRender->WorldToMetersScale);
}
bool FOculusXRHMD::ConvertPose(const FPose& InPose, ovrpPosef& OutPose) const
{
CheckInGameThread();
if (!NextFrameToRender.IsValid())
{
return false;
}
return ConvertPose_Internal(InPose, OutPose, Settings.Get(), NextFrameToRender->WorldToMetersScale);
}
bool FOculusXRHMD::ConvertPose_RenderThread(const ovrpPosef& InPose, FPose& OutPose) const
{
CheckInRenderThread();
if (!Frame_RenderThread.IsValid())
{
return false;
}
return ConvertPose_Internal(InPose, OutPose, Settings_RenderThread.Get(), Frame_RenderThread->WorldToMetersScale);
}
bool FOculusXRHMD::ConvertPose_Internal(const ovrpPosef& InPose, FPose& OutPose, const FSettings* Settings, float WorldToMetersScale)
{
return OculusXRHMD::ConvertPose_Internal(InPose, OutPose, Settings->BaseOrientation, Settings->BaseOffset, WorldToMetersScale);
}
bool FOculusXRHMD::ConvertPose_Internal(const FPose& InPose, ovrpPosef& OutPose, const FSettings* Settings, float WorldToMetersScale)
{
return OculusXRHMD::ConvertPose_Internal(InPose, OutPose, Settings->BaseOrientation, Settings->BaseOffset, WorldToMetersScale);
}
FVector FOculusXRHMD::ScaleAndMovePointWithPlayer(ovrpVector3f& OculusXRHMDPoint)
{
CheckInGameThread();
FMatrix TranslationMatrix;
TranslationMatrix.SetIdentity();
TranslationMatrix = TranslationMatrix.ConcatTranslation(LastPlayerLocation);
FVector ConvertedPoint = ToFVector(OculusXRHMDPoint) * GetWorldToMetersScale();
FRotator RotateWithPlayer = LastPlayerOrientation.Rotator();
FVector TransformWithPlayer = RotateWithPlayer.RotateVector(ConvertedPoint);
TransformWithPlayer = FVector(TranslationMatrix.TransformPosition(TransformWithPlayer));
if (GetXRCamera(HMDDeviceId)->GetUseImplicitHMDPosition())
{
FQuat HeadOrientation = FQuat::Identity;
FVector HeadPosition;
GetCurrentPose(HMDDeviceId, HeadOrientation, HeadPosition);
TransformWithPlayer -= RotateWithPlayer.RotateVector(HeadPosition);
}
return TransformWithPlayer;
}
ovrpVector3f FOculusXRHMD::WorldLocationToOculusPoint(const FVector& InUnrealPosition)
{
CheckInGameThread();
FQuat AdjustedPlayerOrientation = GetBaseOrientation().Inverse() * LastPlayerOrientation;
AdjustedPlayerOrientation.Normalize();
FVector AdjustedPlayerLocation = LastPlayerLocation;
if (GetXRCamera(HMDDeviceId)->GetUseImplicitHMDPosition())
{
FQuat HeadOrientation = FQuat::Identity; // Unused
FVector HeadPosition;
GetCurrentPose(HMDDeviceId, HeadOrientation, HeadPosition);
AdjustedPlayerLocation -= LastPlayerOrientation.Inverse().RotateVector(HeadPosition);
}
const FTransform InvWorldTransform = FTransform(AdjustedPlayerOrientation, AdjustedPlayerLocation).Inverse();
const FVector ConvertedPosition = InvWorldTransform.TransformPosition(InUnrealPosition) / GetWorldToMetersScale();
return ToOvrpVector3f(ConvertedPosition);
}
float FOculusXRHMD::ConvertFloat_M2U(float OculusFloat) const
{
CheckInGameThread();
return OculusFloat * GetWorldToMetersScale();
}
FVector FOculusXRHMD::ConvertVector_M2U(ovrpVector3f OculusXRHMDPoint) const
{
CheckInGameThread();
return ToFVector(OculusXRHMDPoint) * GetWorldToMetersScale();
}
bool FOculusXRHMD::GetUserProfile(UserProfile& OutProfile)
{
float UserIPD;
ovrpVector2f UserNeckEyeDistance;
float UserEyeHeight;
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserIPD2(&UserIPD)) && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserNeckEyeDistance2(&UserNeckEyeDistance)) && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserEyeHeight2(&UserEyeHeight)))
{
OutProfile.IPD = UserIPD;
OutProfile.EyeDepth = UserNeckEyeDistance.x;
OutProfile.EyeHeight = UserEyeHeight;
return true;
}
return false;
}
float FOculusXRHMD::GetVsyncToNextVsync() const
{
CheckInGameThread();
return Settings->VsyncToNextVsync;
}
FPerformanceStats FOculusXRHMD::GetPerformanceStats() const
{
return PerformanceStats;
}
void FOculusXRHMD::GetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel& CpuPerfLevel, EOculusXRProcessorPerformanceLevel& GpuPerfLevel)
{
CheckInGameThread();
CpuPerfLevel = Settings->SuggestedCpuPerfLevel;
GpuPerfLevel = Settings->SuggestedGpuPerfLevel;
}
void FOculusXRHMD::SetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel CpuPerfLevel, EOculusXRProcessorPerformanceLevel GpuPerfLevel)
{
CheckInGameThread();
FOculusXRHMDModule::GetPluginWrapper().SetSuggestedCpuPerformanceLevel((ovrpProcessorPerformanceLevel)CpuPerfLevel);
FOculusXRHMDModule::GetPluginWrapper().SetSuggestedGpuPerformanceLevel((ovrpProcessorPerformanceLevel)GpuPerfLevel);
Settings->SuggestedCpuPerfLevel = CpuPerfLevel;
Settings->SuggestedGpuPerfLevel = GpuPerfLevel;
}
void FOculusXRHMD::SetFoveatedRenderingMethod(EOculusXRFoveatedRenderingMethod InFoveatedRenderingMethod)
{
#ifdef WITH_OCULUS_BRANCH
Settings->FoveatedRenderingMethod = InFoveatedRenderingMethod;
// Don't switch to eye tracked foveated rendering when it's not supported or permissions are denied
if (InFoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering && !(bEyeTrackedFoveatedRenderingSupported && CheckEyeTrackingPermission(InFoveatedRenderingMethod)))
{
return;
}
#else
Settings->FoveatedRenderingMethod = EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering;
if (InFoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering)
{
UE_LOG(LogHMD, Warning, TEXT("Eye Tracked Foveated Rendering is not supported on this engine version, using Fixed Foveated Rendering instead"));
}
#endif // WITH_OCULUS_BRANCH
FoveatedRenderingMethod = Settings->FoveatedRenderingMethod;
}
void FOculusXRHMD::SetFoveatedRenderingLevel(EOculusXRFoveatedRenderingLevel InFoveationLevel, bool isDynamic)
{
FoveatedRenderingLevel = Settings->FoveatedRenderingLevel = InFoveationLevel;
bDynamicFoveatedRendering = Settings->bDynamicFoveatedRendering = isDynamic;
}
void FOculusXRHMD::SetColorScaleAndOffset(FLinearColor ColorScale, FLinearColor ColorOffset, bool bApplyToAllLayers)
{
CheckInGameThread();
Settings->bApplyColorScaleAndOffsetToAllLayers = bApplyToAllLayers;
Settings->ColorScale = LinearColorToOvrpVector4f(ColorScale);
Settings->ColorOffset = LinearColorToOvrpVector4f(ColorOffset);
}
void FOculusXRHMD::SetEnvironmentDepthHandRemoval(bool RemoveHands)
{
FOculusXRHMDModule::GetPluginWrapper().SetEnvironmentDepthHandRemoval(RemoveHands);
bEnvironmentDepthHandRemovalEnabled = RemoveHands;
}
void FOculusXRHMD::StartEnvironmentDepth(int CreateFlags)
{
#if PLATFORM_ANDROID
// Check and request scene permissions (this is needed for environment depth to work)
// bind delegate for handling permission request result
if (!UAndroidPermissionFunctionLibrary::CheckPermission(USE_SCENE_PERMISSION_NAME))
{
TArray<FString> Permissions;
Permissions.Add(USE_SCENE_PERMISSION_NAME);
UAndroidPermissionCallbackProxy* Proxy = UAndroidPermissionFunctionLibrary::AcquirePermissions(Permissions);
static FDelegateHandle DelegateHandle;
DelegateHandle = Proxy->OnPermissionsGrantedDelegate.AddLambda([this, Proxy, CreateFlags](const TArray<FString>& Permissions, const TArray<bool>& GrantResults) {
int PermIndex = Permissions.Find(USE_SCENE_PERMISSION_NAME);
if (PermIndex != INDEX_NONE && GrantResults[PermIndex])
{
UE_LOG(LogHMD, Verbose, TEXT("%s permission granted"), *USE_SCENE_PERMISSION_NAME);
StartEnvironmentDepth(CreateFlags);
}
else
{
UE_LOG(LogHMD, Log, TEXT("%s permission denied"), *USE_SCENE_PERMISSION_NAME);
}
Proxy->OnPermissionsGrantedDelegate.Remove(DelegateHandle);
});
return;
}
#endif // PLATFORM_ANDROID
ExecuteOnRenderThread_DoNotWait([this, CreateFlags]() {
ovrpEnvironmentDepthTextureDesc DepthTextureDesc;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().InitializeEnvironmentDepth(CreateFlags)) && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetEnvironmentDepthTextureDesc(&DepthTextureDesc)))
{
TArray<ovrpTextureHandle> DepthTextures;
int32 TextureCount;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetEnvironmentDepthTextureStageCount(&TextureCount)))
{
// We don't really do different depth texture formats right now and it's always a
// single multiview texture, so no need for a separate right eye texture for now.
// We may need a separate Left/RightDepthTextures in the future.
DepthTextures.SetNum(TextureCount);
for (int32 TextureIndex = 0; TextureIndex < TextureCount; TextureIndex++)
{
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetEnvironmentDepthTexture(TextureIndex, ovrpEye_Left, &DepthTextures[TextureIndex])))
{
UE_LOG(LogHMD, Error, TEXT("Failed to create insight depth texture. NOTE: This causes a leak of %d other texture(s), which will go unused."), TextureIndex);
return;
}
}
uint32 SizeX = DepthTextureDesc.TextureSize.w;
uint32 SizeY = DepthTextureDesc.TextureSize.h;
EPixelFormat DepthFormat = CustomPresent->GetPixelFormat(DepthTextureDesc.Format);
uint32 NumMips = DepthTextureDesc.MipLevels;
uint32 NumSamples = DepthTextureDesc.SampleCount;
uint32 NumSamplesTileMem = 1;
ETextureCreateFlags DepthTexCreateFlags = TexCreate_ShaderResource | TexCreate_InputAttachmentRead;
FClearValueBinding DepthTextureBinding = FClearValueBinding::DepthFar;
ERHIResourceType ResourceType;
if (DepthTextureDesc.Layout == ovrpLayout_Array)
{
ResourceType = RRT_Texture2DArray;
}
else
{
ResourceType = RRT_Texture2D;
}
if (CustomPresent)
{
if (!EnvironmentDepthSwapchain.IsEmpty())
{
EnvironmentDepthSwapchain.Empty();
}
EnvironmentDepthSwapchain = CustomPresent->CreateSwapChainTextures_RenderThread(SizeX, SizeY, DepthFormat, DepthTextureBinding, NumMips, NumSamples, NumSamplesTileMem, ResourceType, DepthTextures, DepthTexCreateFlags, *FString::Printf(TEXT("Oculus Environment Depth Swapchain")));
}
FOculusXRHMDModule::GetPluginWrapper().SetEnvironmentDepthHandRemoval(bEnvironmentDepthHandRemovalEnabled);
FOculusXRHMDModule::GetPluginWrapper().StartEnvironmentDepth();
}
}
});
}
void FOculusXRHMD::StopEnvironmentDepth()
{
ExecuteOnRenderThread_DoNotWait([this]() {
ExecuteOnRHIThread_DoNotWait([this]() {
if (!EnvironmentDepthSwapchain.IsEmpty())
{
EnvironmentDepthSwapchain.Empty();
}
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StopEnvironmentDepth()))
{
FOculusXRHMDModule::GetPluginWrapper().DestroyEnvironmentDepth();
}
});
});
}
bool FOculusXRHMD::IsEnvironmentDepthStarted()
{
return !EnvironmentDepthSwapchain.IsEmpty();
}
void FOculusXRHMD::EnableHardOcclusions(bool bEnable)
{
bHardOcclusionsEnabled = bEnable;
}
bool FOculusXRHMD::DoEnableStereo(bool bStereo)
{
CheckInGameThread();
FSceneViewport* SceneVP = FindSceneViewport();
if (!Settings->Flags.bHMDEnabled || (SceneVP && !SceneVP->IsStereoRenderingAllowed()))
{
bStereo = false;
}
if (Settings->Flags.bStereoEnabled && bStereo || !Settings->Flags.bStereoEnabled && !bStereo)
{
// already in the desired mode
return Settings->Flags.bStereoEnabled;
}
TSharedPtr<SWindow> Window;
if (SceneVP)
{
Window = SceneVP->FindWindow();
}
if (!Window.IsValid() || !SceneVP || !SceneVP->GetViewportWidget().IsValid())
{
// try again next frame
if (bStereo)
{
Flags.bNeedEnableStereo = true;
// a special case when stereo is enabled while window is not available yet:
// most likely this is happening from BeginPlay. In this case, if frame exists (created in OnBeginPlay)
// then we need init device and populate the initial tracking for head/hand poses.
if (Frame.IsValid())
{
InitDevice();
}
}
else
{
Flags.bNeedDisableStereo = true;
}
return Settings->Flags.bStereoEnabled;
}
if (OnOculusStateChange(bStereo))
{
Settings->Flags.bStereoEnabled = bStereo;
// Uncap fps to enable FPS higher than 62
GEngine->bForceDisableFrameRateSmoothing = bStereo;
// Set MirrorWindow state on the Window
Window->SetMirrorWindow(bStereo);
if (bStereo)
{
// Start frame
StartGameFrame_GameThread();
StartRenderFrame_GameThread();
// Set viewport size to Rift resolution
// NOTE: this can enqueue a render frame right away as a result (calling into FOculusXRHMD::BeginRenderViewFamily)
SceneVP->SetViewportSize(Settings->RenderTargetSize.X, Settings->RenderTargetSize.Y);
if (Settings->Flags.bPauseRendering)
{
GEngine->SetMaxFPS(10);
}
}
else
{
// Work around an error log that can happen when enabling stereo rendering again
if (NextFrameNumber == WaitFrameNumber)
{
NextFrameNumber++;
}
if (Settings->Flags.bPauseRendering)
{
GEngine->SetMaxFPS(0);
}
// Restore viewport size to window size
FVector2D size = Window->GetSizeInScreen();
SceneVP->SetViewportSize(size.X, size.Y);
Window->SetViewportSizeDrivenByWindow(true);
}
}
return Settings->Flags.bStereoEnabled;
}
void FOculusXRHMD::ResetControlRotation() const
{
// Switching back to non-stereo mode: reset player rotation and aim.
// Should we go through all playercontrollers here?
APlayerController* pc = GEngine->GetFirstLocalPlayerController(GWorld);
if (pc)
{
// Reset Aim? @todo
FRotator r = pc->GetControlRotation();
r.Normalize();
// Reset roll and pitch of the player
r.Roll = 0;
r.Pitch = 0;
pc->SetControlRotation(r);
}
}
void FOculusXRHMD::UpdateFoveationOffsets_RenderThread()
{
#ifdef WITH_OCULUS_BRANCH
CheckInRenderThread();
SCOPED_NAMED_EVENT(UpdateFoveationOffsets_RenderThread, FColor::Red);
// Don't execute anything if we're not using Eye Tracked Foveated Rendering (this already takes into account if it's supported or not)
if (!Frame_RenderThread.IsValid() || Frame_RenderThread->FoveatedRenderingMethod != EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering)
{
return;
}
const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetSwapChain();
if (!SwapChain.IsValid())
{
return;
}
const FRHITexture2D* const SwapChainTexture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
if (!SwapChainTexture)
{
return;
}
const FIntPoint SwapChainDimensions = SwapChainTexture->GetSizeXY();
// Enqueue the actual update on the RHI thread, which should execute right before the EndRenderPass call
ExecuteOnRHIThread_DoNotWait([this, SwapChainDimensions]() {
SCOPED_NAMED_EVENT(UpdateFoveationEyeTracked_RHIThread, FColor::Red);
bool bUseOffsets = false;
FIntPoint Offsets[2];
// Make sure the the Foveated Rendering Method is still eye tracked at RHI thread time before getting offsets.
// If the base setting was changed to fixed, even if the frame's setting is still eye tracked, we should switch to fixed. This
// usually indicates that eye tracking failed on the previous frame, so we don't need to try it again.
if (Frame_RHIThread.IsValid() && Frame_RHIThread->FoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering && FoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering)
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().SetFoveationEyeTracked(ovrpBool_True)))
{
ovrpVector2f fovCenter[2];
ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetFoveationEyeTrackedCenter(fovCenter);
if (OVRP_SUCCESS(Result))
{
Offsets[0].X = fovCenter[0].x * SwapChainDimensions.X / 2;
Offsets[0].Y = fovCenter[0].y * SwapChainDimensions.Y / 2;
Offsets[1].X = fovCenter[1].x * SwapChainDimensions.X / 2;
Offsets[1].Y = fovCenter[1].y * SwapChainDimensions.Y / 2;
bUseOffsets = true;
}
else if (Result != ovrpFailure_DataIsInvalid)
{
// Fall back to dynamic FFR High if OVRPlugin call actually fails, since we're not expecting GFR to work again.
// Additional rendering changes can be made by binding the changes to OculusEyeTrackingStateChanged
EyeTrackedFoveatedRenderingFallback();
FOculusEventDelegates::OculusEyeTrackingStateChanged.Broadcast(false);
}
}
}
if (CustomPresent)
{
CustomPresent->UpdateFoveationOffsets_RHIThread(bUseOffsets, Offsets);
}
});
#endif // WITH_OCULUS_BRANCH
}
class FHardOcclusionsPS : public FGlobalShader
{
DECLARE_SHADER_TYPE(FHardOcclusionsPS, Global);
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
// This file is already guarded with OCULUS_HMD_SUPPORTED_PLATFORMS
return true;
}
/** Default constructor. */
FHardOcclusionsPS() {}
/** Initialization constructor. */
FHardOcclusionsPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{
EnvironmentDepthTexture.Bind(Initializer.ParameterMap, TEXT("EnvironmentDepthTexture"));
EnvironmentDepthSampler.Bind(Initializer.ParameterMap, TEXT("EnvironmentDepthSampler"));
DepthFactors.Bind(Initializer.ParameterMap, TEXT("DepthFactors"));
ScreenToDepthMatrices.Bind(Initializer.ParameterMap, TEXT("ScreenToDepthMatrices"));
DepthViewId.Bind(Initializer.ParameterMap, TEXT("DepthViewId"));
}
#if UE_VERSION_OLDER_THAN(5, 3, 0)
template <typename TShaderRHIParamRef>
void SetParameters(
FRHICommandListImmediate& RHICmdList,
const TShaderRHIParamRef ShaderRHI,
FRHISamplerState* Sampler,
FRHITexture* Texture,
const FVector2f& Factors,
const FMatrix44f ScreenToDepth[ovrpEye_Count],
const int ViewId)
{
SetTextureParameter(RHICmdList, ShaderRHI, EnvironmentDepthTexture, EnvironmentDepthSampler, Sampler, Texture);
SetShaderValue(RHICmdList, ShaderRHI, DepthFactors, Factors);
SetShaderValueArray(RHICmdList, ShaderRHI, ScreenToDepthMatrices, ScreenToDepth, ovrpEye_Count);
SetShaderValue(RHICmdList, ShaderRHI, DepthViewId, ViewId);
}
#else
void SetParameters(
FRHIBatchedShaderParameters& BatchedParameters,
FRHISamplerState* Sampler,
FRHITexture* Texture,
const FVector2f& Factors,
const FMatrix44f ScreenToDepth[ovrpEye_Count],
const int ViewId)
{
SetTextureParameter(BatchedParameters, EnvironmentDepthTexture, EnvironmentDepthSampler, Sampler, Texture);
SetShaderValue(BatchedParameters, DepthFactors, Factors);
SetShaderValueArray(BatchedParameters, ScreenToDepthMatrices, ScreenToDepth, ovrpEye_Count);
SetShaderValue(BatchedParameters, DepthViewId, ViewId);
}
#endif
private:
LAYOUT_FIELD(FShaderResourceParameter, EnvironmentDepthTexture);
LAYOUT_FIELD(FShaderResourceParameter, EnvironmentDepthSampler);
LAYOUT_FIELD(FShaderParameter, DepthFactors);
LAYOUT_FIELD(FShaderParameter, ScreenToDepthMatrices);
LAYOUT_FIELD(FShaderParameter, DepthViewId);
};
IMPLEMENT_SHADER_TYPE(, FHardOcclusionsPS, TEXT("/Plugin/OculusXR/Private/HardOcclusions.usf"), TEXT("HardOcclusionsPS"), SF_Pixel);
FMatrix44f MakeProjectionMatrix(ovrpFovf cameraFovAngles)
{
const float tanAngleWidth = cameraFovAngles.RightTan + cameraFovAngles.LeftTan;
const float tanAngleHeight = cameraFovAngles.UpTan + cameraFovAngles.DownTan;
FMatrix44f Matrix = FMatrix44f::Identity;
// Scale
Matrix.M[0][0] = 1.0f / tanAngleWidth;
Matrix.M[1][1] = 1.0f / tanAngleHeight;
// Offset
Matrix.M[0][3] = cameraFovAngles.LeftTan / tanAngleWidth;
Matrix.M[1][3] = cameraFovAngles.DownTan / tanAngleHeight;
Matrix.M[2][3] = -1.0f;
return Matrix;
}
FMatrix44f MakeUnprojectionMatrix(ovrpFovf cameraFovAngles)
{
FMatrix44f Matrix = FMatrix44f::Identity;
// Scale
Matrix.M[0][0] = cameraFovAngles.RightTan + cameraFovAngles.LeftTan;
Matrix.M[1][1] = cameraFovAngles.UpTan + cameraFovAngles.DownTan;
// Offset
Matrix.M[0][3] = -cameraFovAngles.LeftTan;
Matrix.M[1][3] = -cameraFovAngles.DownTan;
Matrix.M[2][3] = 1.0;
return Matrix;
}
bool FOculusXRHMD::ComputeEnvironmentDepthParameters_RenderThread(FVector2f& DepthFactors, FMatrix44f ScreenToDepth[ovrpEye_Count], FMatrix44f DepthViewProj[ovrpEye_Count], int& SwapchainIndex)
{
float ScreenNearZ = GNearClippingPlane / Frame_RenderThread->WorldToMetersScale;
ovrpFovf* ScreenFov = Frame_RenderThread->SymmetricFov;
ovrpEnvironmentDepthFrameDesc DepthFrameDesc[ovrpEye_Count];
if (FOculusXRHMDModule::GetPluginWrapper().GetEnvironmentDepthFrameDesc(ovrpEye_Left, &DepthFrameDesc[0]) != ovrpSuccess || !DepthFrameDesc[0].IsValid)
{
return false;
}
if (FOculusXRHMDModule::GetPluginWrapper().GetEnvironmentDepthFrameDesc(ovrpEye_Right, &DepthFrameDesc[1]) != ovrpSuccess || !DepthFrameDesc[1].IsValid)
{
return false;
}
SwapchainIndex = DepthFrameDesc[0].SwapchainIndex;
const float WorldToMetersScale = Frame_RenderThread->WorldToMetersScale;
if (DepthViewProj != nullptr)
{
for (int i = 0; i < ovrpEye_Count; ++i)
{
ovrpFrustum2f DepthFrustum;
DepthFrustum.Fov = DepthFrameDesc[i].Fov;
DepthFrustum.zNear = DepthFrameDesc[i].NearZ * WorldToMetersScale;
DepthFrustum.zFar = DepthFrameDesc[i].FarZ * WorldToMetersScale;
FMatrix DepthProjectionMatrix = ToFMatrix(ovrpMatrix4f_Projection(DepthFrustum, true));
auto DepthOrientation = ToFQuat(DepthFrameDesc[i].CreatePose.Orientation);
// NOTE: This matrix is the same as applied in SetupViewFrustum in SceneView.cpp
auto ViewMatrix = DepthOrientation.Inverse().ToMatrix() * FMatrix(FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, 0, 1));
ovrpPoseStatef EyePoseState;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, Frame_RenderThread->FrameNumber, (ovrpNode)i, &EyePoseState)))
{
auto DepthTranslation = ToFVector(DepthFrameDesc[i].CreatePose.Position) * WorldToMetersScale;
auto EyePos = ToFVector(EyePoseState.Pose.Position) * WorldToMetersScale;
auto Delta = EyePos - DepthTranslation;
// NOTE: The view matrix here is relative to the VR camera, this is necessary to support
// Large Worlds and avoid rounding errors when getting very far away from the origin
ViewMatrix = ViewMatrix.ConcatTranslation(ViewMatrix.TransformPosition(Delta));
}
DepthViewProj[i] = (FMatrix44f)(ViewMatrix * DepthProjectionMatrix);
}
}
// Assume NearZ and FarZ are the same for left and right eyes
float DepthNearZ = DepthFrameDesc[ovrpEye_Left].NearZ;
float DepthFarZ = DepthFrameDesc[ovrpEye_Left].FarZ;
float Scale;
float Offset;
if (DepthFarZ < DepthNearZ || (!FGenericPlatformMath::IsFinite(DepthFarZ)))
{
// Inf far plane:
Scale = DepthNearZ;
Offset = 0.0f;
}
else
{
// Finite far plane:
Scale = (DepthFarZ * DepthNearZ) / (DepthFarZ - DepthNearZ);
Offset = DepthNearZ / (DepthFarZ - DepthNearZ);
}
DepthFactors.X = -ScreenNearZ / Scale;
DepthFactors.Y = Offset * ScreenNearZ / Scale + 1.0f;
// The pose extrapolated to the predicted display time of the current frame
FQuat ScreenOrientation = Frame_RenderThread->HeadOrientation;
for (int i = 0; i < ovrpEye_Count; ++i)
{
// Screen To Depth represents the transformation matrix used to map normalised screen UV coordinates to
// normalised environment depth texture UV coordinates. This needs to account for 2 things:
// 1. The field of view of the two textures may be different, Unreal typically renders using a symmetric fov.
// That is to say the FOV of the left and right eyes is the same. The environment depth on the other hand
// has a different FOV for the left and right eyes. So we need to scale and offset accordingly to account
// for this difference.
auto T_ScreenCamera_ScreenNormCoord = MakeUnprojectionMatrix(ScreenFov[i]);
auto T_DepthNormCoord_DepthCamera = MakeProjectionMatrix(DepthFrameDesc[i].Fov);
// 2. The headset may have moved in between capturing the environment depth and rendering the frame. We
// can only account for rotation of the headset, not translation.
auto DepthOrientation = ToFQuat(DepthFrameDesc[i].CreatePose.Orientation);
if (!DepthOrientation.IsNormalized())
{
UE_LOG(LogHMD, Error, TEXT("DepthOrientation is not normalized %f %f %f %f"), DepthOrientation.X, DepthOrientation.Y, DepthOrientation.Z, DepthOrientation.W);
DepthOrientation.Normalize();
}
auto ScreenToDepthQuat = ScreenOrientation.Inverse() * DepthOrientation;
FMatrix44f R_DepthCamera_ScreenCamera = FQuat4f(ScreenToDepthQuat.Y, ScreenToDepthQuat.Z, ScreenToDepthQuat.X, ScreenToDepthQuat.W).GetNormalized().ToMatrix();
ScreenToDepth[i] = T_DepthNormCoord_DepthCamera * R_DepthCamera_ScreenCamera * T_ScreenCamera_ScreenNormCoord;
}
return true;
}
#if !UE_VERSION_OLDER_THAN(5, 3, 0)
BEGIN_SHADER_PARAMETER_STRUCT(FDrawRectangleParameters, )
SHADER_PARAMETER(FVector4f, PosScaleBias)
SHADER_PARAMETER(FVector4f, UVScaleBias)
SHADER_PARAMETER(FVector4f, InvTargetSizeAndTextureSize)
END_SHADER_PARAMETER_STRUCT()
#endif
void FOculusXRHMD::DrawHmdViewMesh(
FRHICommandList& RHICmdList,
float X,
float Y,
float SizeX,
float SizeY,
float U,
float V,
float SizeU,
float SizeV,
FIntPoint TargetSize,
FIntPoint TextureSize,
int32 StereoView,
const TShaderRef<FShader>& VertexShader)
{
FDrawRectangleParameters Parameters;
Parameters.PosScaleBias = FVector4f(SizeX, SizeY, X, Y);
Parameters.UVScaleBias = FVector4f(SizeU, SizeV, U, V);
Parameters.InvTargetSizeAndTextureSize = FVector4f(
1.0f / TargetSize.X, 1.0f / TargetSize.Y,
1.0f / TextureSize.X, 1.0f / TextureSize.Y);
#if UE_VERSION_OLDER_THAN(5, 3, 0)
SetUniformBufferParameterImmediate(RHICmdList, VertexShader.GetVertexShader(), VertexShader->GetUniformBufferParameter<FDrawRectangleParameters>(), Parameters);
#else
FRHIBatchedShaderParameters& BatchedParameters = RHICmdList.GetScratchShaderParameters();
SetUniformBufferParameterImmediate(BatchedParameters, VertexShader->GetUniformBufferParameter<FDrawRectangleParameters>(), Parameters);
RHICmdList.SetBatchedShaderParameters(VertexShader.GetVertexShader(), BatchedParameters);
#endif
RendererModule->DrawRectangle(
RHICmdList,
X, Y,
SizeX, SizeY,
0, 0,
TextureSize.X, TextureSize.Y,
TargetSize,
TextureSize,
VertexShader);
}
#if UE_VERSION_OLDER_THAN(5, 3, 0)
void FOculusXRHMD::RenderHardOcclusions_RenderThread(FRHICommandListImmediate& RHICmdList, const FSceneView& InView)
#else
void FOculusXRHMD::RenderHardOcclusions_RenderThread(FRHICommandList& RHICmdList, const FSceneView& InView)
#endif
{
checkSlow(RHICmdList.IsInsideRenderPass());
FVector2f DepthFactors;
FMatrix44f ScreenToDepthMatrices[ovrpEye_Count];
int SwapchainIndex;
if (!Frame_RenderThread.IsValid() || InView.bIsSceneCapture || InView.bIsReflectionCapture || InView.bIsPlanarReflection || !ComputeEnvironmentDepthParameters_RenderThread(DepthFactors, ScreenToDepthMatrices, nullptr, SwapchainIndex))
{
return;
}
if (SwapchainIndex >= EnvironmentDepthSwapchain.Num())
{
return;
}
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<>::GetRHI();
FGlobalShaderMap* GlobalShaderMap = GetGlobalShaderMap(InView.FeatureLevel);
TShaderMapRef<FScreenPassVS> VertexShader(GlobalShaderMap);
TShaderMapRef<FHardOcclusionsPS> PixelShader(GlobalShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
FRHITexture* DepthTexture = EnvironmentDepthSwapchain[SwapchainIndex];
FRHISamplerState* DepthSampler = TStaticSamplerState<>::GetRHI();
FIntPoint TextureSize = DepthTexture->GetDesc().Extent;
FIntRect ScreenRect = InView.UnscaledViewRect;
#if UE_VERSION_OLDER_THAN(5, 3, 0)
PixelShader->SetParameters(RHICmdList, PixelShader.GetPixelShader(), DepthSampler, DepthTexture, DepthFactors, ScreenToDepthMatrices, InView.StereoViewIndex);
#else
FRHIBatchedShaderParameters& BatchedParameters = RHICmdList.GetScratchShaderParameters();
PixelShader->SetParameters(BatchedParameters, DepthSampler, DepthTexture, DepthFactors, ScreenToDepthMatrices, InView.StereoViewIndex);
RHICmdList.SetBatchedShaderParameters(PixelShader.GetPixelShader(), BatchedParameters);
#endif
check(Settings->CurrentShaderPlatform == InView.Family->Scene->GetShaderPlatform());
if (!IsMobilePlatform(Settings->CurrentShaderPlatform) && InView.StereoViewIndex != INDEX_NONE)
{
SCOPED_DRAW_EVENTF(RHICmdList, RenderHardOcclusions_RenderThread, TEXT("View %d"), InView.StereoViewIndex);
int32 width = ScreenRect.Width() / 2;
int32 height = ScreenRect.Height();
int32 x = InView.StereoViewIndex == EStereoscopicEye::eSSE_LEFT_EYE ? 0 : width;
int32 y = 0;
DrawHmdViewMesh(
RHICmdList,
x, y,
width, height,
0, 0,
TextureSize.X, TextureSize.Y,
FIntPoint(ScreenRect.Width(), ScreenRect.Height()),
TextureSize,
InView.StereoViewIndex,
VertexShader);
}
else
{
SCOPED_DRAW_EVENT(RHICmdList, RenderHardOcclusions_RenderThread);
RendererModule->DrawRectangle(
RHICmdList,
0, 0,
ScreenRect.Width(), ScreenRect.Height(),
0, 0,
TextureSize.X, TextureSize.Y,
FIntPoint(ScreenRect.Width(), ScreenRect.Height()),
TextureSize,
VertexShader);
}
}
FSettingsPtr FOculusXRHMD::CreateNewSettings() const
{
FSettingsPtr Result(MakeShareable(new FSettings()));
return Result;
}
FGameFramePtr FOculusXRHMD::CreateNewGameFrame() const
{
FGameFramePtr Result(MakeShareable(new FGameFrame()));
Result->FrameNumber = NextFrameNumber;
Result->WindowSize = CachedWindowSize;
Result->WorldToMetersScale = CachedWorldToMetersScale;
Result->NearClippingPlane = GNearClippingPlane;
// Allow CVars to override the app's foveated rendering settings (set -1 to restore app's setting)
Result->FoveatedRenderingMethod = CVarOculusFoveatedRenderingMethod.GetValueOnAnyThread() >= 0 ? (EOculusXRFoveatedRenderingMethod)CVarOculusFoveatedRenderingMethod.GetValueOnAnyThread() : FoveatedRenderingMethod.load();
Result->FoveatedRenderingLevel = CVarOculusFoveatedRenderingLevel.GetValueOnAnyThread() >= 0 ? (EOculusXRFoveatedRenderingLevel)CVarOculusFoveatedRenderingLevel.GetValueOnAnyThread() : FoveatedRenderingLevel.load();
Result->bDynamicFoveatedRendering = CVarOculusDynamicFoveatedRendering.GetValueOnAnyThread() >= 0 ? (bool)CVarOculusDynamicFoveatedRendering.GetValueOnAnyThread() : bDynamicFoveatedRendering.load();
Result->Flags.bSplashIsShown = Splash->IsShown();
return Result;
}
void FOculusXRHMD::StartGameFrame_GameThread()
{
CheckInGameThread();
check(Settings.IsValid());
// TODO: Below check should NOT be limited to bMultiPlayer specificially even though we haven't found a non-MultiPlayer case falling into this case yet.
// Remove bMultiPlayer.
if (bMultiPlayer && !bShouldWait_GameThread)
{
return;
}
if (!Frame.IsValid())
{
Splash->UpdateLoadingScreen_GameThread(); //the result of this is used in CreateGameFrame to know if Frame is a "real" one or a "splash" one.
if (Settings->Flags.bHMDEnabled)
{
Frame = CreateNewGameFrame();
NextFrameToRender = Frame;
UE_LOG(LogHMD, VeryVerbose, TEXT("StartGameFrame %u"), Frame->FrameNumber);
if (!Splash->IsShown())
{
FThreadIdleStats::FScopeIdle Scope;
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && WaitFrameNumber != Frame->FrameNumber)
{
SCOPED_NAMED_EVENT(WaitFrame, FColor::Red);
UE_LOG(LogHMD, Verbose, TEXT("FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame %u"), Frame->FrameNumber);
ovrpResult Result;
if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame(Frame->FrameNumber)))
{
UE_LOG(LogHMD, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame %u failed (%d)"), Frame->FrameNumber, Result);
}
else
{
WaitFrameNumber = Frame->FrameNumber;
bShouldWait_GameThread = false;
}
}
FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, Frame->FrameNumber, 0.0);
}
}
UpdateStereoRenderingParams();
}
}
void FOculusXRHMD::FinishGameFrame_GameThread()
{
CheckInGameThread();
if (Frame.IsValid())
{
UE_LOG(LogHMD, VeryVerbose, TEXT("FinishGameFrame %u"), Frame->FrameNumber);
}
Frame.Reset();
}
void FOculusXRHMD::StartRenderFrame_GameThread()
{
CheckInGameThread();
if (NextFrameToRender.IsValid() && NextFrameToRender != LastFrameToRender)
{
UE_LOG(LogHMD, VeryVerbose, TEXT("StartRenderFrame %u"), NextFrameToRender->FrameNumber);
LastFrameToRender = NextFrameToRender;
NextFrameToRender->Flags.bSplashIsShown = Splash->IsShown();
ovrpXrApi NativeXrApi;
FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi);
if ((NextFrameToRender->ShowFlags.Rendering || NativeXrApi == ovrpXrApi_OpenXR) && !NextFrameToRender->Flags.bSplashIsShown)
{
NextFrameNumber++;
}
FSettingsPtr XSettings = Settings->Clone();
FGameFramePtr XFrame = NextFrameToRender->Clone();
TArray<FLayerPtr> XLayers;
XLayers.Empty(LayerMap.Num());
for (auto Pair : LayerMap)
{
XLayers.Emplace(Pair.Value->Clone());
}
XLayers.Sort(FLayerPtr_CompareId());
ExecuteOnRenderThread_DoNotWait([this, XSettings, XFrame, XLayers](FRHICommandListImmediate& RHICmdList) {
if (XFrame.IsValid())
{
Settings_RenderThread = XSettings;
Frame_RenderThread = XFrame;
int32 XLayerIndex = 0;
int32 LayerIndex_RenderThread = 0;
TArray<FLayerPtr> ValidXLayers;
while (XLayerIndex < XLayers.Num() && LayerIndex_RenderThread < Layers_RenderThread.Num())
{
uint32 LayerIdA = XLayers[XLayerIndex]->GetId();
uint32 LayerIdB = Layers_RenderThread[LayerIndex_RenderThread]->GetId();
if (LayerIdA < LayerIdB)
{
if (XLayers[XLayerIndex]->Initialize_RenderThread(Settings_RenderThread.Get(), CustomPresent, &DeferredDeletion, RHICmdList))
{
ValidXLayers.Add(XLayers[XLayerIndex]);
}
XLayerIndex++;
}
else if (LayerIdA > LayerIdB)
{
DeferredDeletion.AddLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]);
}
else
{
if (XLayers[XLayerIndex]->Initialize_RenderThread(Settings_RenderThread.Get(), CustomPresent, &DeferredDeletion, RHICmdList, Layers_RenderThread[LayerIndex_RenderThread].Get()))
{
LayerIndex_RenderThread++;
ValidXLayers.Add(XLayers[XLayerIndex]);
}
XLayerIndex++;
}
}
while (XLayerIndex < XLayers.Num())
{
if (XLayers[XLayerIndex]->Initialize_RenderThread(Settings_RenderThread.Get(), CustomPresent, &DeferredDeletion, RHICmdList))
{
ValidXLayers.Add(XLayers[XLayerIndex]);
}
XLayerIndex++;
}
while (LayerIndex_RenderThread < Layers_RenderThread.Num())
{
DeferredDeletion.AddLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]);
}
Layers_RenderThread = ValidXLayers;
DeferredDeletion.HandleLayerDeferredDeletionQueue_RenderThread();
}
});
}
}
void FOculusXRHMD::FinishRenderFrame_RenderThread(FRDGBuilder& GraphBuilder)
{
CheckInRenderThread();
// TODO: Below check should NOT be limited to bMultiPlayer specificially even though we haven't found a non-MultiPlayer case falling into this case yet.
// Remove bMultiPlayer.
if (bMultiPlayer && !bIsRendering_RenderThread)
{ //we must keep Frame_RenderThread alive if we haven't started to use it to render yet!
return;
}
if (Frame_RenderThread.IsValid())
{
UE_LOG(LogHMD, VeryVerbose, TEXT("FinishRenderFrame %u"), Frame_RenderThread->FrameNumber);
AddPass(GraphBuilder, RDG_EVENT_NAME("FinishRenderFrame"), [this](FRHICommandListImmediate& RHICmdList) {
if (Frame_RenderThread->ShowFlags.Rendering)
{
for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++)
{
Layers_RenderThread[LayerIndex]->UpdateTexture_RenderThread(Settings_RenderThread.Get(), CustomPresent, RHICmdList);
Layers_RenderThread[LayerIndex]->UpdatePassthrough_RenderThread(CustomPresent, RHICmdList, Frame_RenderThread.Get());
}
}
Frame_RenderThread.Reset();
});
}
bIsRendering_RenderThread = false;
}
void FOculusXRHMD::StartRHIFrame_RenderThread()
{
CheckInRenderThread();
if (Frame_RenderThread.IsValid())
{
UE_LOG(LogHMD, VeryVerbose, TEXT("StartRHIFrame %u"), Frame_RenderThread->FrameNumber);
FSettingsPtr XSettings = Settings_RenderThread->Clone();
FGameFramePtr XFrame = Frame_RenderThread->Clone();
TArray<FLayerPtr> XLayers = Layers_RenderThread;
for (int32 XLayerIndex = 0; XLayerIndex < XLayers.Num(); XLayerIndex++)
{
XLayers[XLayerIndex] = XLayers[XLayerIndex]->Clone();
}
ExecuteOnRHIThread_DoNotWait([this, XSettings, XFrame, XLayers]() {
if (XFrame.IsValid())
{
Settings_RHIThread = XSettings;
Frame_RHIThread = XFrame;
Layers_RHIThread = XLayers;
ovrpXrApi NativeXrApi;
FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi);
if ((Frame_RHIThread->ShowFlags.Rendering || NativeXrApi == ovrpXrApi_OpenXR) && !Frame_RHIThread->Flags.bSplashIsShown)
{
SCOPED_NAMED_EVENT(BeginFrame, FColor::Red);
UE_LOG(LogHMD, Verbose, TEXT("FOculusXRHMDModule::GetPluginWrapper().BeginFrame4 %u"), Frame_RHIThread->FrameNumber);
ovrpResult Result;
if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().BeginFrame4(Frame_RHIThread->FrameNumber, CustomPresent->GetOvrpCommandQueue())))
{
UE_LOG(LogHMD, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().BeginFrame4 %u failed (%d)"), Frame_RHIThread->FrameNumber, Result);
Frame_RHIThread->ShowFlags.Rendering = false;
}
else
{
#if PLATFORM_ANDROID
FOculusXRHMDModule::GetPluginWrapper().SetTiledMultiResLevel((ovrpTiledMultiResLevel)Frame_RHIThread->FoveatedRenderingLevel);
FOculusXRHMDModule::GetPluginWrapper().SetTiledMultiResDynamic(Frame_RHIThread->bDynamicFoveatedRendering ? ovrpBool_True : ovrpBool_False);
#ifdef WITH_OCULUS_BRANCH
// If we're using eye tracked foveated rendering, set that at the end of the render pass instead (through UpdateFoveationOffsets_RenderThread)
if (Frame_RHIThread->FoveatedRenderingMethod != EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering)
{
FOculusXRHMDModule::GetPluginWrapper().SetFoveationEyeTracked(ovrpBool_False);
// Need to also not use offsets when turning off eye tracked foveated rendering
if (CustomPresent)
{
CustomPresent->UpdateFoveationOffsets_RHIThread(false, nullptr);
}
}
#endif // WITH_OCULUS_BRANCH
#endif // PLATFORM_ANDROID
}
}
}
});
// TODO: Add a hook to resolve discarded frames before we start a new frame.
// TODO: Below check should NOT be limited to bMultiPlayer specificially even though we haven't found a non-MultiPlayer case falling into this case yet.
// Remove bMultiPlayer.
UE_CLOG(bMultiPlayer && bIsRendering_RenderThread, LogHMD, Verbose, TEXT("Discarded previous frame and started rendering a new frame."));
bIsRendering_RenderThread = true;
}
}
void FOculusXRHMD::FinishRHIFrame_RHIThread()
{
CheckInRHIThread();
if (Frame_RHIThread.IsValid())
{
UE_LOG(LogHMD, VeryVerbose, TEXT("FinishRHIFrame %u"), Frame_RHIThread->FrameNumber);
ovrpXrApi NativeXrApi;
FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi);
if ((Frame_RHIThread->ShowFlags.Rendering || NativeXrApi == ovrpXrApi_OpenXR) && !Frame_RHIThread->Flags.bSplashIsShown)
{
SCOPED_NAMED_EVENT(EndFrame, FColor::Red);
TArray<FLayerPtr> Layers = Layers_RHIThread;
Layers.Sort(FLayerPtr_CompareTotal());
TArray<const ovrpLayerSubmit*> LayerSubmitPtr;
int32 LayerNum = Layers.Num();
LayerSubmitPtr.SetNum(LayerNum);
int32 FinalLayerNumber = 0;
for (int32 LayerIndex = 0; LayerIndex < LayerNum; LayerIndex++)
{
if (Layers[LayerIndex]->IsVisible())
{
LayerSubmitPtr[FinalLayerNumber++] = Layers[LayerIndex]->UpdateLayer_RHIThread(Settings_RHIThread.Get(), Frame_RHIThread.Get(), LayerIndex);
}
}
UE_LOG(LogHMD, Verbose, TEXT("FOculusXRHMDModule::GetPluginWrapper().EndFrame4 %u"), Frame_RHIThread->FrameNumber);
FOculusXRHMDModule::GetPluginWrapper().SetEyeFovPremultipliedAlphaMode(false);
ovrpResult Result;
if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().EndFrame4(Frame_RHIThread->FrameNumber, LayerSubmitPtr.GetData(), FinalLayerNumber, CustomPresent->GetOvrpCommandQueue())))
{
UE_LOG(LogHMD, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().EndFrame4 %u failed (%d)"), Frame_RHIThread->FrameNumber, Result);
}
else
{
for (int32 LayerIndex = 0; LayerIndex < Layers.Num(); LayerIndex++)
{
Layers[LayerIndex]->IncrementSwapChainIndex_RHIThread(CustomPresent);
}
}
}
}
Frame_RHIThread.Reset();
}
void FOculusXRHMD::AddEventPollingDelegate(const FOculusXRHMDEventPollingDelegate& NewDelegate)
{
EventPollingDelegates.Add(NewDelegate);
}
/// @cond DOXYGEN_WARNINGS
#define BOOLEAN_COMMAND_HANDLER_BODY(ConsoleName, FieldExpr) \
do \
{ \
if (Args.Num()) \
{ \
if (Args[0].Equals(TEXT("toggle"), ESearchCase::IgnoreCase)) \
{ \
(FieldExpr) = !(FieldExpr); \
} \
else \
{ \
(FieldExpr) = FCString::ToBool(*Args[0]); \
} \
} \
Ar.Logf(ConsoleName TEXT(" = %s"), (FieldExpr) ? TEXT("On") : TEXT("Off")); \
} \
while (false)
void FOculusXRHMD::UpdateOnRenderThreadCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar)
{
CheckInGameThread();
BOOLEAN_COMMAND_HANDLER_BODY(TEXT("vr.oculus.bUpdateOnRenderThread"), Settings->Flags.bUpdateOnRT);
}
void FOculusXRHMD::PixelDensityMinCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar)
{
CheckInGameThread();
if (Args.Num())
{
Settings->SetPixelDensityMin(FCString::Atof(*Args[0]));
}
Ar.Logf(TEXT("vr.oculus.PixelDensity.min = \"%1.2f\""), Settings->PixelDensityMin);
}
void FOculusXRHMD::PixelDensityMaxCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar)
{
CheckInGameThread();
if (Args.Num())
{
Settings->SetPixelDensityMax(FCString::Atof(*Args[0]));
}
Ar.Logf(TEXT("vr.oculus.PixelDensity.max = \"%1.2f\""), Settings->PixelDensityMax);
}
void FOculusXRHMD::HQBufferCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar)
{
CheckInGameThread();
BOOLEAN_COMMAND_HANDLER_BODY(TEXT("vr.oculus.bHQBuffer"), Settings->Flags.bHQBuffer);
}
void FOculusXRHMD::HQDistortionCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar)
{
CheckInGameThread();
BOOLEAN_COMMAND_HANDLER_BODY(TEXT("vr.oculus.bHQDistortion"), Settings->Flags.bHQDistortion);
}
void FOculusXRHMD::ShowGlobalMenuCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar)
{
CheckInGameThread();
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().ShowSystemUI2(ovrpUI::ovrpUI_GlobalMenu)))
{
Ar.Logf(TEXT("Could not show platform menu"));
}
}
void FOculusXRHMD::ShowQuitMenuCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar)
{
CheckInGameThread();
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().ShowSystemUI2(ovrpUI::ovrpUI_ConfirmQuit)))
{
Ar.Logf(TEXT("Could not show platform menu"));
}
}
#if !UE_BUILD_SHIPPING
void FOculusXRHMD::StatsCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar)
{
CheckInGameThread();
BOOLEAN_COMMAND_HANDLER_BODY(TEXT("vr.oculus.Debug.bShowStats"), Settings->Flags.bShowStats);
}
void FOculusXRHMD::ShowSettingsCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar)
{
Ar.Logf(TEXT("stereo ipd=%.4f\n nearPlane=%.4f"), GetInterpupillaryDistance(), GNearClippingPlane);
}
void FOculusXRHMD::IPDCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar)
{
if (Args.Num() > 0)
{
SetInterpupillaryDistance(FCString::Atof(*Args[0]));
}
Ar.Logf(TEXT("vr.oculus.Debug.IPD = %f"), GetInterpupillaryDistance());
}
#endif // !UE_BUILD_SHIPPING
void FOculusXRHMD::LoadFromSettings()
{
UOculusXRHMDRuntimeSettings* HMDSettings = GetMutableDefault<UOculusXRHMDRuntimeSettings>();
check(HMDSettings);
Settings->Flags.bSupportsDash = HMDSettings->bSupportsDash;
#if PLATFORM_ANDROID
Settings->Flags.bCompositeDepth = HMDSettings->bCompositeDepthMobile;
#else
Settings->Flags.bCompositeDepth = HMDSettings->bCompositesDepth;
#endif
Settings->Flags.bHQDistortion = HMDSettings->bHQDistortion;
Settings->Flags.bInsightPassthroughEnabled = HMDSettings->bInsightPassthroughEnabled;
#ifdef WITH_OCULUS_BRANCH
Settings->Flags.bPixelDensityAdaptive = HMDSettings->bDynamicResolution;
#endif
Settings->SuggestedCpuPerfLevel = HMDSettings->SuggestedCpuPerfLevel;
Settings->SuggestedGpuPerfLevel = HMDSettings->SuggestedGpuPerfLevel;
Settings->FoveatedRenderingMethod = HMDSettings->FoveatedRenderingMethod;
Settings->FoveatedRenderingLevel = HMDSettings->FoveatedRenderingLevel;
Settings->bDynamicFoveatedRendering = HMDSettings->bDynamicFoveatedRendering;
Settings->PixelDensityMin = HMDSettings->PixelDensityMin;
Settings->PixelDensityMax = HMDSettings->PixelDensityMax;
Settings->ColorSpace = HMDSettings->ColorSpace;
Settings->ControllerPoseAlignment = HMDSettings->ControllerPoseAlignment;
Settings->bLateLatching = HMDSettings->bLateLatching;
Settings->XrApi = HMDSettings->XrApi;
Settings->bSupportExperimentalFeatures = HMDSettings->bSupportExperimentalFeatures;
Settings->bSupportEyeTrackedFoveatedRendering = HMDSettings->bSupportEyeTrackedFoveatedRendering;
Settings->SystemSplashBackground = HMDSettings->SystemSplashBackground;
Settings->FaceTrackingDataSource.Empty(ovrpFaceConstants_FaceTrackingDataSourcesCount);
Settings->FaceTrackingDataSource.Append(HMDSettings->FaceTrackingDataSource);
}
void FOculusXRHMD::CheckMultiPlayer()
{
#if WITH_EDITOR && PLATFORM_WINDOWS
ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault<ULevelEditorPlaySettings>();
check(PlayInSettings);
int PlayNumberOfClients = 0;
PlayInSettings->GetPlayNumberOfClients(PlayNumberOfClients);
bMultiPlayer = PlayNumberOfClients > 1;
#endif
}
/// @endcond
} // namespace OculusXRHMD
#endif //OCULUS_HMD_SUPPORTED_PLATFORMS