October3d55/M/PICOXRPlugin/Source/PICOXRHMD/Private/PXR_HMD.cpp

3373 lines
106 KiB
C++

// Copyright PICO Technology Co., Ltd. All rights reserved.
// This plugin incorporates portions of the Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere.
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PXR_HMD.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "PXR_HMDPrivateRHI.h"
#include "PXR_HMDRenderBridge.h"
#include "PXR_HMDRuntimeSettings.h"
#include "XRThreadUtils.h"
#include "PXR_Input.h"
#include "IHeadMountedDisplayVulkanExtensions.h"
#include "PXR_Log.h"
#include "PXR_StereoLayer.h"
#include "PXR_HMDFunctionLibrary.h"
#include "GameFramework/WorldSettings.h"
#include "Misc/EngineVersion.h"
#include "PXR_Utils.h"
#include "HardwareInfo.h"
#include "SceneRendering.h"
#include "Misc/CoreDelegates.h"
#include "GameFramework/GameUserSettings.h"
#include "PICO_MRCSceneCapture2D.h"
#define PICO_PAUSED_IDLE_FPS 10
static TAutoConsoleVariable<int32> CVarPICOEnableSubsampledLayout(
TEXT("r.Mobile.PICO.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> CVarPICOEnableSuperResolution(
TEXT("r.Mobile.PICO.EnableSuperResolution"),
0,
TEXT("0: Disable SuperResolution (Default)\n")
TEXT("1: Enable SuperResolution on supported platforms\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarPICOSharpeningSetting(
TEXT("r.Mobile.PICO.SharpeningSetting"),
0,
TEXT("0: Disable Sharpening (Default)\n")
TEXT("1: Enable NormalSharpening on supported platforms\n")
TEXT("2: Enable QualitySharpening on supported platforms\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarPICOSharpeningEnhanceMode(
TEXT("r.Mobile.PICO.SharpeningEnhanceMode"),
0,
TEXT("0: Disable Sharpening EnhanceMode (Default)\n")
TEXT("1: Enable Fixed Foveated on supported platforms\n")
TEXT("2: Enable Adaptive on supported platforms\n")
TEXT("3: Enable FixedFoveated and Adaptive on supported platforms\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
#ifdef PICO_CUSTOM_ENGINE
//PICO AppSpaceWarp
static TAutoConsoleVariable<int32> CVarPICOEnableSpaceWarpUser(
TEXT("r.Mobile.PICO.SpaceWarp.Enable"),
0,
TEXT("0 Disable spacewarp at runtime.\n")
TEXT("1 Enable spacewarp at runtime.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarPICOEnableSpaceWarpInternal(
TEXT("r.Mobile.PICO.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);
static TAutoConsoleVariable<int32> CVarPICOEnableStaticSpaceWarpUser(
TEXT("r.Mobile.PICO.StaticSpaceWarp.Enable"),
0,
TEXT("0 Disable Static spacewarp.\n")
TEXT("1 Enable Static spacewarp.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<bool> CVarPICOEnableEyeTrackedFoveatedRendering(
TEXT("r.Mobile.PICO.EnableEyeTrackedFoveatedRendering"),
true,
TEXT("Whether to use eye tracked foveated rendering"),
ECVF_Default);
#endif
static TAutoConsoleVariable<int32> CVarPICOBlendModeSetting(
TEXT("r.Mobile.PICO.BlendModeSetting"),
1,
TEXT("0: Covering Mode, VST will cover the entire screen\n")
TEXT("1: Clip Mode,Eye Buffer and VST will clip by Alpha Before add(Default)\n")
TEXT("2: Additive Mode, Eye Buffer will not clip by Alpha\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
float FPICOXRHMD::IpdValue = 0.f;
FName FPICOXRHMD::GetSystemName() const
{
static FName DefaultName(TEXT("PICOXRHMD"));
return DefaultName;
}
bool FPICOXRHMD::EnumerateTrackedDevices(TArray<int32>& OutDevices, EXRTrackedDeviceType Type /*= EXRTrackedDeviceType::Any*/)
{
if (Type == EXRTrackedDeviceType::Any || Type == EXRTrackedDeviceType::HeadMountedDisplay)
{
OutDevices.Add(HMDDeviceId);
return true;
}
return false;
}
void FPICOXRHMD::SetInterpupillaryDistance(float NewIPD)
{
//TODO:SetIPD(NewIPD);
}
float FPICOXRHMD::GetInterpupillaryDistance() const
{
return UPxr_GetIPD();
}
bool FPICOXRHMD::IsHMDConnected()
{
CheckInGameThread();
return GameSettings->Flags.bHMDEnabled && IsPICOHMDConnected();
}
bool FPICOXRHMD::GetRelativeEyePose(int32 InDeviceId, int32 InEye, FQuat& OutOrientation, FVector& OutPosition)
{
OutOrientation = FQuat::Identity;
OutPosition = FVector::ZeroVector;
if (InDeviceId != HMDDeviceId)
{
return false;
}
FPXRGameFrame* CurrentFrame;
if (IsInRenderingThread())
{
CurrentFrame = GameFrame_RenderThread.Get();
}
else if (IsInGameThread())
{
CurrentFrame = NextGameFrameToRender_GameThread.Get();
}
else
{
return false;
}
if (!CurrentFrame)
{
return false;
}
if (InDeviceId == HMDDeviceId && (InEye == eSSE_LEFT_EYE || InEye == eSSE_RIGHT_EYE))
{
OutPosition = FVector(0, (InEye == eSSE_LEFT_EYE ? -.5 : .5) * GetInterpupillaryDistance() * CurrentFrame->WorldToMetersScale, 0);
#if PLATFORM_ANDROID
if (FPICOXRVersionHelper::IsThisVersionOrGreater(0x2000306))
{
PxrQuaternionf quaternion;
quaternion.x = 0;
quaternion.y = 0;
quaternion.z = 0;
quaternion.w = 1.0f;
switch (InEye)
{
case eSSE_LEFT_EYE:
{
FPICOXRHMDModule::GetPluginWrapper().GetEyeOrientation((int)0, &quaternion);
}
break;
case eSSE_RIGHT_EYE:
{
FPICOXRHMDModule::GetPluginWrapper().GetEyeOrientation((int)1, &quaternion);
}
break;
default:
break;
}
OutOrientation=FQuat(quaternion.z,quaternion.x,quaternion.y,quaternion.w);
}
#endif
return true;
}
return false;
}
bool FPICOXRHMD::GetTrackingSensorProperties(int32 InDeviceId, FQuat& OutOrientation, FVector& OutPosition, FXRSensorProperties& OutSensorProperties)
{
check(IsInGameThread());
FPICOXRFrustum FOV;
switch (InDeviceId)
{
case 0:
FOV.FovLeft = (LeftFrustum.FovLeft + RightFrustum.FovLeft) / 2;
FOV.FovRight = (LeftFrustum.FovRight + RightFrustum.FovRight) / 2;
FOV.FovUp = (LeftFrustum.FovUp + RightFrustum.FovUp) / 2;
FOV.FovDown = (LeftFrustum.FovDown + RightFrustum.FovDown) / 2;
FOV.Near = (LeftFrustum.Near + RightFrustum.Near) / 2;
FOV.Far = (LeftFrustum.Far + RightFrustum.Far) / 2;
break;
case 1:
FOV = LeftFrustum;
break;
case 2:
FOV = RightFrustum;
break;
default:
return false;
break;
}
if (InDeviceId == 0)
{
if (GetCurrentPose(InDeviceId, OutOrientation, OutPosition))
{
OutSensorProperties.LeftFOV = FMath::RadiansToDegrees(FOV.FovLeft);
OutSensorProperties.RightFOV = FMath::RadiansToDegrees(FOV.FovRight);
OutSensorProperties.TopFOV = FMath::RadiansToDegrees(FOV.FovUp);
OutSensorProperties.BottomFOV = FMath::RadiansToDegrees(FOV.FovDown);
OutSensorProperties.NearPlane = FOV.Near * NextGameFrameToRender_GameThread->WorldToMetersScale;
OutSensorProperties.FarPlane = FOV.Far * NextGameFrameToRender_GameThread->WorldToMetersScale;
OutSensorProperties.CameraDistance = 1.0f * NextGameFrameToRender_GameThread->WorldToMetersScale;
return true;
}
}
return false;
}
void FPICOXRHMD::ResetOrientationAndPosition(float Yaw /*= 0.f*/)
{
Recenter(RecenterOrientationAndPosition, Yaw);
}
void FPICOXRHMD::ResetOrientation(float Yaw /*= 0.f*/)
{
Recenter(RecenterOrientation, Yaw);
}
void FPICOXRHMD::ResetPosition()
{
Recenter(RecenterPosition, 0);
}
bool FPICOXRHMD::GetCurrentPose(int32 DeviceId, FQuat& CurrentOrientation, FVector& CurrentPosition)
{
if (DeviceId != HMDDeviceId)
{
return false;
}
CurrentOrientation = FQuat::Identity;
CurrentPosition = FVector::ZeroVector;
FPXRGameFrame* CurrentFrame = NULL;
if (IsInRenderingThread())
{
CurrentFrame = GameFrame_RenderThread.Get();
LateUpdatePose();
}
else if (IsInGameThread())
{
CurrentFrame = NextGameFrameToRender_GameThread.Get();
}
else
{
return false;
}
if (CurrentFrame)
{
CurrentOrientation = CurrentFrame->Orientation;
CurrentPosition = CurrentFrame->Position;
PXR_LOGV(PxrUnreal, "GetCurrentPose Frame:%u Rotation:%s,Position:%s", CurrentFrame->FrameNumber, PLATFORM_CHAR(*CurrentOrientation.Rotator().ToString()), PLATFORM_CHAR(*CurrentPosition.ToString()));
return true;
}
return false;
}
void FPICOXRHMD::UPxr_GetAngularAcceleration(FVector& AngularAcceleration)
{
FPXRGameFrame* CurrentFrame = NULL;
if (IsInGameThread())
{
CurrentFrame = NextGameFrameToRender_GameThread.Get();
}
else if (IsInRenderingThread())
{
CurrentFrame = GameFrame_RenderThread.Get();
}
if (CurrentFrame)
{
AngularAcceleration = CurrentFrame->AngularAcceleration;
}
else
{
AngularAcceleration = FVector::ZeroVector;
}
}
void FPICOXRHMD::UPxr_GetVelocity(FVector& Velocity)
{
FPXRGameFrame* CurrentFrame = NULL;
if (IsInGameThread())
{
CurrentFrame = NextGameFrameToRender_GameThread.Get();
}
else if (IsInRenderingThread())
{
CurrentFrame = GameFrame_RenderThread.Get();
}
if (CurrentFrame)
{
Velocity = CurrentFrame->Velocity;
}
else
{
Velocity = FVector::ZeroVector;
}
}
void FPICOXRHMD::UPxr_GetAcceleration(FVector& Acceleration)
{
FPXRGameFrame* CurrentFrame = NULL;
if (IsInGameThread())
{
CurrentFrame = NextGameFrameToRender_GameThread.Get();
}
else if (IsInRenderingThread())
{
CurrentFrame = GameFrame_RenderThread.Get();
}
if (CurrentFrame)
{
Acceleration = CurrentFrame->Acceleration;
}
else
{
Acceleration = FVector::ZeroVector;
}
}
void FPICOXRHMD::UPxr_GetAngularVelocity(FVector& AngularVelocity)
{
FPXRGameFrame* CurrentFrame = NULL;
if (IsInGameThread())
{
CurrentFrame = NextGameFrameToRender_GameThread.Get();
}
else if (IsInRenderingThread())
{
CurrentFrame = GameFrame_RenderThread.Get();
}
if (CurrentFrame)
{
AngularVelocity = CurrentFrame->AngularVelocity;
}
else
{
AngularVelocity = FVector::ZeroVector;
}
}
int32 FPICOXRHMD::UPxr_SetFieldOfView(EPICOXREyeType Eye, float FovLeftInDegrees, float FovRightInDegrees, float FovUpInDegrees, float FovDownInDegrees)
{
int res = 0;
#if PLATFORM_ANDROID
if (FPICOXRVersionHelper::IsThisVersionOrGreater(0x2000300))
{
PxrConfigType type;
switch (Eye)
{
case EPICOXREyeType::LeftEye:
{
type = PxrConfigType::PXR_LEFT_EYE_FOV;
}
break;
case EPICOXREyeType::RightEye:
{
type = PxrConfigType::PXR_RIGHT_EYE_FOV;
}
break;
case EPICOXREyeType::BothEye:
{
type = PxrConfigType::PXR_BOTH_EYE_FOV;
}
break;
default:;
}
TArray<float> FovData;
FovData.Add(-FMath::DegreesToRadians(FovLeftInDegrees));
FovData.Add(FMath::DegreesToRadians(FovRightInDegrees));
FovData.Add(FMath::DegreesToRadians(FovUpInDegrees));
FovData.Add(-FMath::DegreesToRadians(FovDownInDegrees));
res = FPICOXRHMDModule::GetPluginWrapper().SetConfigFloatArray(type, FovData.GetData(), 4);
}
#endif
return res;
}
FString FPICOXRHMD::UPxr_GetDeviceModel()
{
return DeviceModel;
}
void FPICOXRHMD::SetBaseRotation(const FRotator& BaseRot)
{
SetBaseOrientation(BaseRot.Quaternion());
}
FRotator FPICOXRHMD::GetBaseRotation() const
{
return GetBaseOrientation().Rotator();
}
void FPICOXRHMD::SetBaseOrientation(const FQuat& BaseOrient)
{
CheckInGameThread();
GameSettings->BaseOrientation = BaseOrient;
}
FQuat FPICOXRHMD::GetBaseOrientation() const
{
CheckInGameThread();
return GameSettings->BaseOrientation;
}
void FPICOXRHMD::SetTrackingOrigin(EHMDTrackingOrigin::Type NewOrigin)
{
TrackingOrigin = NewOrigin;
PxrTrackingOrigin Origin = PxrTrackingOrigin::PXR_EYE_LEVEL;
switch (NewOrigin)
{
case EHMDTrackingOrigin::View:
{
PXR_LOGD(PxrUnreal, "SetTrackingOrigin:EHMDTrackingOrigin::Eye");
Origin = PxrTrackingOrigin::PXR_EYE_LEVEL;
break;
}
case EHMDTrackingOrigin::LocalFloor:
{
PXR_LOGD(PxrUnreal, "SetTrackingOrigin:EHMDTrackingOrigin::Floor");
Origin = PxrTrackingOrigin::PXR_FLOOR_LEVEL;
break;
}
case EHMDTrackingOrigin::Stage:
{
PXR_LOGD(PxrUnreal, "SetTrackingOrigin:EHMDTrackingOrigin::Stage");
Origin = PxrTrackingOrigin::PXR_STAGE_LEVEL;
break;
}
default:
break;
}
#if PLATFORM_ANDROID
if (FPICOXRHMDModule::GetPluginWrapper().bIsSessionInitialized)
{
EHMDTrackingOrigin::Type lastOrigin = GetTrackingOrigin();
FPICOXRHMDModule::GetPluginWrapper().SetTrackingOrigin(Origin);
PICOFlags.NeedSetTrackingOrigin = false;
if (lastOrigin != NewOrigin)
{
GameSettings->BaseOffset = FVector::ZeroVector;
}
}
#endif
OnTrackingOriginChanged();
}
EHMDTrackingOrigin::Type FPICOXRHMD::GetTrackingOrigin() const
{
EHMDTrackingOrigin::Type to = EHMDTrackingOrigin::View;
#if PLATFORM_ANDROID
PxrTrackingOrigin Origin = PxrTrackingOrigin::PXR_EYE_LEVEL;
if (FPICOXRHMDModule::GetPluginWrapper().bIsSessionInitialized && FPICOXRHMDModule::GetPluginWrapper().GetTrackingOrigin(&Origin) == 0)
{
switch (Origin)
{
case PxrTrackingOrigin::PXR_EYE_LEVEL:
to = EHMDTrackingOrigin::View;
break;
case PxrTrackingOrigin::PXR_FLOOR_LEVEL:
to = EHMDTrackingOrigin::LocalFloor;
break;
case PxrTrackingOrigin::PXR_STAGE_LEVEL:
to = EHMDTrackingOrigin::Stage;
break;
default:
PXR_LOGE(PxrUnreal, "Unsupported ovr tracking origin type %d", int(Origin));
break;
}
}
#endif
return to;
}
class TSharedPtr< class IStereoRendering, ESPMode::ThreadSafe > FPICOXRHMD::GetStereoRenderingDevice()
{
return SharedThis(this);
}
float FPICOXRHMD::GetWorldToMetersScale() const
{
if (NextGameFrameToRender_GameThread.IsValid())
{
return NextGameFrameToRender_GameThread->WorldToMetersScale;
}
if (GWorld != nullptr)
{
return GWorld->GetWorldSettings()->WorldToMeters;
}
return 100.0f;
}
bool FPICOXRHMD::DoesSupportPositionalTracking() const
{
return !PICOXRSetting->bIsHMD3Dof;
}
bool FPICOXRHMD::IsHeadTrackingAllowed() const
{
return true;
}
bool FPICOXRHMD::IsActiveThisFrame_Internal(const FSceneViewExtensionContext& Context) const
{
return true;
}
bool FPICOXRHMD::IsHMDEnabled() const
{
CheckInGameThread();
return (GameSettings->Flags.bHMDEnabled);
}
void FPICOXRHMD::EnableHMD(bool Allow /*= true*/)
{
CheckInGameThread();
GameSettings->Flags.bHMDEnabled = Allow;
if (!GameSettings->Flags.bHMDEnabled)
{
EnableStereo(false);
}
}
bool FPICOXRHMD::GetHMDMonitorInfo(MonitorInfo &MonitorDesc)
{
check(IsInGameThread());
MonitorDesc.MonitorName = FString("PICOXR Window");
MonitorDesc.MonitorId = 0;
MonitorDesc.DesktopX = MonitorDesc.DesktopY = 0;
MonitorDesc.ResolutionX = MonitorDesc.ResolutionY = 0;
MonitorDesc.WindowSizeX = MonitorDesc.WindowSizeY = 0;
if (GameSettings.IsValid())
{
MonitorDesc.ResolutionX = MonitorDesc.WindowSizeX = GameSettings->RenderTargetSize.X;
MonitorDesc.ResolutionY = MonitorDesc.WindowSizeY = GameSettings->RenderTargetSize.Y;
}
return true;
}
void FPICOXRHMD::GetFieldOfView(float& OutHFOVInDegrees, float& OutVFOVInDegrees) const
{
const float LeftHFOVInDegrees = FMath::RadiansToDegrees(LeftFrustum.FovRight - LeftFrustum.FovLeft);
const float LeftVFOVInDegrees = FMath::RadiansToDegrees(LeftFrustum.FovUp - LeftFrustum.FovDown);
const float RightHFOVInDegrees = FMath::RadiansToDegrees(RightFrustum.FovRight - RightFrustum.FovLeft);
const float RightVFOVInDegrees = FMath::RadiansToDegrees(RightFrustum.FovUp - RightFrustum.FovDown);
OutHFOVInDegrees = (LeftHFOVInDegrees+RightHFOVInDegrees)/2;
OutVFOVInDegrees = (LeftVFOVInDegrees+RightVFOVInDegrees)/2;
}
bool FPICOXRHMD::IsChromaAbCorrectionEnabled() const
{
return false;
}
FIntPoint FPICOXRHMD::GetIdealRenderTargetSize() const
{
PXR_LOGV(PxrUnreal, "IdealRenderTargetSize:(%d,%d)", IdealRenderTargetSize.X, IdealRenderTargetSize.Y);
return IdealRenderTargetSize;
}
FIntPoint FPICOXRHMD::GetMaxRenderTargetSize() const
{
FIntPoint MaxRenderTargetSize(4096 * 2, 4096);
PXR_LOGV(PxrUnreal, "MaxRenderTargetSize:(%d,%d)", MaxRenderTargetSize.X, MaxRenderTargetSize.Y);
return MaxRenderTargetSize;
}
void FPICOXRHMD::OnBeginRendering_GameThread()
{
}
void FPICOXRHMD::OnBeginRendering_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily)
{
CheckInRenderThread();
if (!GameFrame_RenderThread.IsValid())
{
return;
}
if (!GameSettings_RenderThread.IsValid() || !GameSettings_RenderThread->IsStereoEnabled())
{
return;
}
#if PLATFORM_ANDROID
FAndroidApplication::GetJavaEnv();
#endif
OnRHIFrameBegin_RenderThread();
}
bool FPICOXRHMD::OnStartGameFrame(FWorldContext& WorldContext)
{
PXR_LOGV(PxrUnreal, "OnStartGameFrame");
if (IsEngineExitRequested())
{
return false;
}
RefreshTrackingToWorldTransform(WorldContext);
check(GameSettings.IsValid());
if (!GameSettings->IsStereoEnabled())
{
FApp::SetUseVRFocus(false);
FApp::SetHasVRFocus(false);
}
if (bShutdownRequestQueued)
{
bShutdownRequestQueued = false;
DoSessionShutdown();
}
if (!WorldContext.World() || (!(GEnableVREditorHacks && WorldContext.WorldType == EWorldType::Editor) && !WorldContext.World()->IsGameWorld()))
{
return false;
}
CachedWorldToMetersScale = WorldContext.World()->GetWorldSettings()->WorldToMeters;
OnGameFrameBegin_GameThread();
#if PLATFORM_ANDROID
if (FPICOXRHMDModule::GetPluginWrapper().bIsSessionInitialized)
{
if (PICOFlags.NeedSetTrackingOrigin)
{
SetTrackingOrigin(TrackingOrigin);
}
bool bAppHasVRFocus = true;
//bAppHasVRFocus = FPICOXRHMDModule::GetPluginWrapper().GetAppHasFocus();
if (GameSettings->SeeThroughState == 2)
{
//bAppHasVRFocus = false;
}
FApp::SetUseVRFocus(true);
FApp::SetHasVRFocus(bAppHasVRFocus != false);
if (!GIsEditor)
{
if (!bAppHasVRFocus)
{
// not visible,
if (!GameSettings->Flags.bPauseRendering)
{
PXR_LOGI(PxrUnreal, "The app went out of VR focus, seizing rendering...");
}
}
else if (GameSettings->Flags.bPauseRendering)
{
PXR_LOGI(PxrUnreal, "The app got VR focus, restoring rendering...");
}
bool bPrevPause = GameSettings->Flags.bPauseRendering;
GameSettings->Flags.bPauseRendering = !bAppHasVRFocus;
if (GameSettings->Flags.bPauseRendering && (GEngine->GetMaxFPS() != PICO_PAUSED_IDLE_FPS))
{
GEngine->SetMaxFPS(PICO_PAUSED_IDLE_FPS);
}
if (bPrevPause != GameSettings->Flags.bPauseRendering)
{
APlayerController* const PC = GEngine->GetFirstLocalPlayerController(WorldContext.World());
if (GameSettings->Flags.bPauseRendering)
{
// focus is lost
GEngine->SetMaxFPS(PICO_PAUSED_IDLE_FPS);
if (!FCoreDelegates::ApplicationWillEnterBackgroundDelegate.IsBound())
{
PICOFlags.AppIsPaused = false;
if (PC && !PC->IsPaused())
{
PC->SetPause(true);
PICOFlags.AppIsPaused = true;
}
}
else
{
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.Broadcast();
}
}
else
{
GEngine->SetMaxFPS(0);
if (!FCoreDelegates::ApplicationHasEnteredForegroundDelegate.IsBound())
{
if (PC && PICOFlags.AppIsPaused)
{
PC->SetPause(false);
}
PICOFlags.AppIsPaused = false;
}
else
{
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.Broadcast();
}
}
}
}
}
#endif
if (MRCEnabled && MRCCamera == nullptr)
{
FActorSpawnParameters SpawnInfo;
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnInfo.bNoFail = true;
SpawnInfo.ObjectFlags = RF_Transient;
MRCCamera = WorldContext.World()->SpawnActor<AMRCSceneCapture2DPICO>(SpawnInfo);
MRCCamera->SetActorEnableCollision(false);
PXR_LOGI(LogMRC, "Created MRC Camera!");
}
else if(!MRCEnabled && MRCCamera != nullptr)
{
MRCCamera->Destroy();
MRCCamera = nullptr;
PXR_LOGI(LogMRC, "Destroyed MRC Camera!");
}
return true;
}
bool FPICOXRHMD::OnEndGameFrame(FWorldContext& WorldContext)
{
FPXRGameFrame* const CurrentGameFrame = GameFrame_GameThread.Get();
if (CurrentGameFrame)
{
CurrentGameFrame->TrackingToWorld = ComputeTrackingToWorldTransform(WorldContext);
#ifdef PICO_CUSTOM_ENGINE
CurrentGameFrame->LastTrackingToWorld = LastTrackingToWorld;
LastTrackingToWorld = CurrentGameFrame->TrackingToWorld;
#endif
}
else
{
return false;
}
OnGameFrameEnd_GameThread();
return true;
}
bool FPICOXRHMD::IsStereoEnabled() const
{
if (IsInGameThread())
{
return GameSettings.IsValid() && GameSettings->IsStereoEnabled();
}
if (IsInRenderingThread())
{
return GameSettings_RenderThread.IsValid() && GameSettings_RenderThread->IsStereoEnabled();
}
return true;
}
bool FPICOXRHMD::EnableStereo(bool Stereo /*= true*/)
{
GameSettings->Flags.bStereoEnabled = Stereo;
return GameSettings->Flags.bStereoEnabled;
}
void FPICOXRHMD::AdjustViewRect(int32 ViewIndex, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const
{
if (GameSettings.IsValid())
{
X = GameSettings->EyeUnscaledRenderViewport[ViewIndex].Min.X;
Y = GameSettings->EyeUnscaledRenderViewport[ViewIndex].Min.Y;
SizeX = GameSettings->EyeUnscaledRenderViewport[ViewIndex].Size().X;
SizeY = GameSettings->EyeUnscaledRenderViewport[ViewIndex].Size().Y;
}
else
{
SizeX = SizeX / 2;
if (ViewIndex == eSSE_RIGHT_EYE)
{
X += SizeX;
}
}
PXR_LOGV(PxrUnreal,"AdjustViewRect StereoPass:%d ,X: %d,Y: %d ,SizeX: %d,SizeY: %d)", ViewIndex, X, Y, SizeX, SizeY);
}
void FPICOXRHMD::SetFinalViewRect(FRHICommandListImmediate& RHICmdList, const int32 ViewIndex, const FIntRect& FinalViewRect)
{
CheckInRenderThread();
if (GameSettings_RenderThread.IsValid() && GameSettings_RenderThread->Flags.bPixelDensityAdaptive)
{
GameSettings_RenderThread->EyeRenderViewport[ViewIndex] = FinalViewRect;
}
ExecuteOnRHIThread_DoNotWait([this, ViewIndex, FinalViewRect]()
{
CheckInRHIThread();
if (GameSettings_RHIThread.IsValid() && GameSettings_RHIThread->Flags.bPixelDensityAdaptive)
{
GameSettings_RHIThread->EyeRenderViewport[ViewIndex] = FinalViewRect;
}
});
}
int32 FPICOXRHMD::AcquireColorTexture()
{
if (FPICOXRHMDModule::GetPluginWrapper().IsRunning())
{
const FXRSwapChainPtr& ColorSwapchain = RenderBridge->SwapChain;
if (ColorSwapchain)
{
const FXRSwapChainPtr& SwapChain = PXREyeLayer_RenderThread->GetSwapChain();
SwapChain->IncrementSwapChainIndex_RHIThread();
return SwapChain->GetSwapChainIndex_RHIThread();
}
}
return 0;
}
FMatrix FPICOXRHMD::GetStereoProjectionMatrix(int32 StereoPassType) const
{
FPICOXRFrustum Frustum = (StereoPassType == eSSE_LEFT_EYE) ? LeftFrustum : RightFrustum;
const float ProjectionCenterOffset = 0;// 0.151976421f;
const float PassProjectionOffset = (StereoPassType == eSSE_LEFT_EYE) ? ProjectionCenterOffset : -ProjectionCenterOffset;
// 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 ZNear = GNearClippingPlane * WorldScale;
Frustum.FovUp = tan(Frustum.FovUp);
Frustum.FovDown = tan(Frustum.FovDown);
Frustum.FovLeft = tan(Frustum.FovLeft);
Frustum.FovRight = tan(Frustum.FovRight);
float SumRL = (Frustum.FovRight + Frustum.FovLeft);
float SumTB = (Frustum.FovUp + Frustum.FovDown);
float InvRL = (1.0f / (Frustum.FovRight - Frustum.FovLeft));
float InvTB = (1.0f / (Frustum.FovUp - Frustum.FovDown));
FMatrix ProjectionMatrix = FMatrix(
FPlane((2.0f * InvRL), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, (2.0f * InvTB), 0.0f, 0.0f),
FPlane((SumRL * -InvRL), (SumTB * -InvTB), 0.0f, 1.0f),
FPlane(0.0f, 0.0f, ZNear, 0.0f)) * FTranslationMatrix(FVector(PassProjectionOffset, 0, 0));
return ProjectionMatrix;
}
void FPICOXRHMD::GetEyeRenderParams_RenderThread(const FHeadMountedDisplayPassContext& Context, FVector2D& EyeToSrcUVScaleValue, FVector2D& EyeToSrcUVOffsetValue) const
{
if (Context.View.StereoViewIndex== eSSE_LEFT_EYE)
{
EyeToSrcUVOffsetValue.X = 0.0f;
EyeToSrcUVOffsetValue.Y = 0.0f;
EyeToSrcUVScaleValue.X = 0.5f;
EyeToSrcUVScaleValue.Y = 1.0f;
}
else
{
EyeToSrcUVOffsetValue.X = 0.5f;
EyeToSrcUVOffsetValue.Y = 0.0f;
EyeToSrcUVScaleValue.X = 0.5f;
EyeToSrcUVScaleValue.Y = 1.0f;
}
}
FPICOXRHMD::FPICOXRHMD(const FAutoRegister&AutoRegister)
: FHeadMountedDisplayBase(nullptr)
, FSceneViewExtensionBase(AutoRegister)
, inputFocusState(true)
, DisplayRefreshRate(72.0f)
, CurrentHMDBatteryLevel(0)
, bSeeThroughIsShown(false)
, bIsUsingMobileMultiView(false)
, CurrentCoordinateType(EPICOXRCoordinateType::Local)
, IdealRenderTargetSize(FIntPoint::ZeroValue)
, RenderBridge(nullptr)
, PICOXRSetting(nullptr)
, bIsEndGameFrame(false)
, TrackingOrigin(EHMDTrackingOrigin::View)
, PlayerController(nullptr)
, PICOSplash(nullptr)
, ContentResourceFinder(nullptr)
, bShutdownRequestQueued(false)
{
PICOFlags.Raw = 0;
EventManager = UPICOXREventManager::GetInstance();
PICOXRSetting = GetMutableDefault<UPICOXRSettings>();
#if PLATFORM_ANDROID
DeviceModel = FAndroidMisc::GetDeviceModel();
#endif
NextGameFrameNumber = 1;
WaitedFrameNumber = 0;
NextLayerId = 0;
GameSettings = CreateNewSettings();
GameSettings_RenderThread =CreateNewSettings();
}
FPICOXRHMD::~FPICOXRHMD()
{
Shutdown();
if (bShutdownRequestQueued)
{
DoSessionShutdown();
}
}
void FPICOXRHMD::BeginXR()
{
#if PLATFORM_ANDROID
//Frustum
FPICOXRHMDModule::GetPluginWrapper().GetFrustum(PXR_EYE_LEFT, &LeftFrustum.FovLeft, &LeftFrustum.FovRight, &LeftFrustum.FovUp, &LeftFrustum.FovDown, &LeftFrustum.Near, &LeftFrustum.Far);
FPICOXRHMDModule::GetPluginWrapper().GetFrustum(PXR_EYE_RIGHT, &RightFrustum.FovLeft, &RightFrustum.FovRight, &RightFrustum.FovUp, &RightFrustum.FovDown, &RightFrustum.Near, &RightFrustum.Far);
LeftFrustum.FovLeft = FMath::Atan(LeftFrustum.FovLeft / LeftFrustum.Near);
LeftFrustum.FovRight = FMath::Atan(LeftFrustum.FovRight / LeftFrustum.Near);
LeftFrustum.FovUp = FMath::Atan(LeftFrustum.FovUp / LeftFrustum.Near);
LeftFrustum.FovDown = FMath::Atan(LeftFrustum.FovDown / LeftFrustum.Near);
RightFrustum.FovLeft = FMath::Atan(RightFrustum.FovLeft / RightFrustum.Near);
RightFrustum.FovRight = FMath::Atan(RightFrustum.FovRight / RightFrustum.Near);
RightFrustum.FovUp = FMath::Atan(RightFrustum.FovUp / RightFrustum.Near);
RightFrustum.FovDown = FMath::Atan(RightFrustum.FovDown / RightFrustum.Near);
IpdValue = FPICOXRHMDModule::GetPluginWrapper().GetIPD();
PXR_LOGI(PxrUnreal, "Startup Get ipd = %f", IpdValue);
ExecuteOnRenderThread([this]()
{
ExecuteOnRHIThread([this]()
{
if (!FPICOXRHMDModule::GetPluginWrapper().IsRunning())
{
if (true)
{
SetRefreshRate();
PXR_LOGI(PxrUnreal, "BeginXr Call!");
FPICOXRHMDModule::GetPluginWrapper().BeginXr();
PXR_LOGI(PxrUnreal, "BeginXr Return!");
float RefreshRate = 72.0f;
FPICOXRHMDModule::GetPluginWrapper().GetDisplayRefreshRate(&RefreshRate);
DisplayRefreshRate = RefreshRate != 0 ? RefreshRate : 72.0f;
PXR_LOGI(PxrUnreal, "Pxr_GetDisplayRefreshRate:%f", DisplayRefreshRate);
}
else
{
PXR_LOGF(PxrUnreal, "BeginVR: Pxr_CanBeginVR return false");
}
}
});
});
FPICOXRHMDModule::GetPluginWrapper().SetControllerEnableKey(PICOXRSetting->bEnableHomeKey, PxrControllerKeyMap::PXR_CONTROLLER_KEY_HOME);
uint32_t device;
FPICOXRHMDModule::GetPluginWrapper().GetControllerMainInputHandle(&device);
#endif
}
void FPICOXRHMD::EndXR()
{
#if PLATFORM_ANDROID
FPICOXRHMDModule::GetPluginWrapper().SetControllerEnableKey(false, PxrControllerKeyMap::PXR_CONTROLLER_KEY_HOME);
if (IsInGameThread())
{
ExecuteOnRenderThread([this]()
{
ExecuteOnRHIThread([this]()
{
PXR_LOGI(PxrUnreal, "EndXr Call!");
FPICOXRHMDModule::GetPluginWrapper().EndXr();
PXR_LOGI(PxrUnreal, "EndXr Return!");
});
});
}
#endif
}
FPICOXRInput* FPICOXRHMD::GetPICOXRInput()
{
TArray<IMotionController*> MotionControllers = IModularFeatures::Get().GetModularFeatureImplementations<IMotionController>(IMotionController::GetModularFeatureName());
for (auto MotionController : MotionControllers)
{
if (MotionController != nullptr && MotionController->GetMotionControllerDeviceTypeName() == FName(TEXT("PICOXRInput")))
{
return static_cast<FPICOXRInput*>(MotionController);
}else
{
PXR_LOGE(PxrUnreal,"GetPICOXRInput failed");
}
}
return nullptr;
}
FPXRGameFramePtr FPICOXRHMD::MakeNewGameFrame() const
{
FPXRGameFramePtr Result(MakeShareable(new FPXRGameFrame()));
Result->FrameNumber = NextGameFrameNumber;
Result->predictedDisplayTimeMs = CurrentFramePredictedTime + 1000.0f / DisplayRefreshRate;
Result->WorldToMetersScale = CachedWorldToMetersScale;
Result->Flags.bSplashIsShown = PICOSplash->IsShown();
Result->Flags.bSeeThroughIsShown=bSeeThroughIsShown;
Result->Flags.bHasWaited = NextGameFrameNumber == WaitedFrameNumber ? true : false;
return Result;
}
void FPICOXRHMD::UpdateStereoRenderingParams()
{
CheckInGameThread();
FPICOLayerPtr* EyeLayerFound = PXRLayerMap.Find(0);
FPICOXRStereoLayer* EyeLayer = new FPICOXRStereoLayer(**EyeLayerFound);
*EyeLayerFound = MakeShareable(EyeLayer);
uint32 Layout = 1;
#ifdef PICO_CUSTOM_ENGINE
static const auto CVarMobileToneMapping = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.TonemapSubpass"));
if (bIsMobileToneMapping!= (CVarMobileToneMapping->GetValueOnAnyThread()==1))
{
bIsMobileToneMapping=(CVarMobileToneMapping->GetValueOnAnyThread()==1);
if (bIsMobileToneMapping)
{
const FString LogBuff="pico_tone_mapping|enabled";
FPICOXRHMDModule::GetPluginWrapper().LogSdkApi(TCHAR_TO_UTF8(*LogBuff));
PXR_LOGI(PxrUnreal, "Performance tracking has been triggered:pico_tone_mapping|enabled");
}
}
#endif
static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView"));
const bool bIsMobileMultiViewEnabled = (CVarMobileMultiView && CVarMobileMultiView->GetValueOnAnyThread() != 0);
bIsUsingMobileMultiView = (GSupportsMobileMultiView || GRHISupportsArrayIndexFromAnyShader) && bIsMobileMultiViewEnabled;
PXR_LOGV(PxrUnreal, "vr.MobileMultiView=%d,bIsUsingMobileMultiView=%d", bIsMobileMultiViewEnabled, bIsUsingMobileMultiView);
#if PLATFORM_ANDROID
if (bIsUsingMobileMultiView && IsMobilePlatform(GameSettings->CurrentShaderPlatform))
{
FPICOXRHMDModule::GetPluginWrapper().EnableMultiview(true);
Layout = 2;
}
else
{
FPICOXRHMDModule::GetPluginWrapper().EnableMultiview(false);
}
#endif
static const auto CVarMobileHDR = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MobileHDR"));
const bool bMobileHDR = CVarMobileHDR && CVarMobileHDR->GetValueOnAnyThread() == 1;
if (bMobileHDR)
{
static bool bDisplayedHDRError = false;
bDisplayedHDRError = true;
}
if (!bIsUsingMobileMultiView && GameSettings->bLateLatching)
{
GameSettings->bLateLatching = false;
}
// Update render target and viewport
UpdateRenderTargetAndViewport();
{
if (RHIString == TEXT("Vulkan"))
{
//Vulkan needs to detect whether there is a change in tracking mode. If there is a change, it needs to recreate the SwapChain of FFR.
#ifdef PICO_CUSTOM_ENGINE
FPICOXRHMDModule::GetPluginWrapper().GetEyeTrackingFoveationRenderingState(&EyeLayer->bEnableEyeTrackingFoveationRendering);
#endif
}
const bool EnableSubsampled = CVarPICOEnableSubsampledLayout.GetValueOnAnyThread() == 1 && GameSettings->FoveatedRenderingLevel != PxrFoveationLevel::PXR_FOVEATION_LEVEL_NONE;
EyeLayer->SetEyeLayerDesc(GameSettings->RenderTargetSize.X, GameSettings->RenderTargetSize.Y, Layout, 1, 1, RHIString, EnableSubsampled);
EyeLayer->bNeedsTexSrgbCreate = GameSettings->Flags.bsRGBEyeBuffer;
}
if (!EyeLayer->IfCanReuseLayers(PXREyeLayer_RenderThread.Get()))
{
AllocateEyeLayer();
}
#ifdef PICO_CUSTOM_ENGINE
//PICO AppSpaceWarp
if (IsSupportsSpaceWarp())
{
bool spaceWarpEnabledByUser = CVarPICOEnableSpaceWarpUser.GetValueOnAnyThread()!=0;
bool spaceWarpEnabledInternal = CVarPICOEnableSpaceWarpInternal.GetValueOnAnyThread()!=0;
if (spaceWarpEnabledByUser != spaceWarpEnabledInternal)
{
CVarPICOEnableSpaceWarpInternal->Set(spaceWarpEnabledByUser);
CVarPICOEnableStaticSpaceWarpUser->Set(spaceWarpEnabledByUser);
}
}
#endif
}
bool FPICOXRHMD::Startup()
{
PXR_LOGI(PxrUnreal, "Startup");
check(!RenderBridge.IsValid());
FString HardwareDetails = FHardwareInfo::GetHardwareDetailsString();
FString RHILookup = NAME_RHI.ToString() + TEXT("=");
if (!FParse::Value(*HardwareDetails, *RHILookup, RHIString))
{
return false;
}
#if PICO_HMD_SUPPORTED_PLATFORMS_OPENGL
if (RHIString == TEXT("OpenGL"))
{
PXR_LOGI(PxrUnreal, "RHIString OpenGL");
RenderBridge = CreateRenderBridge_OpenGL(this);
}
else
#endif
#if PICO_HMD_SUPPORTED_PLATFORMS_VULKAN
if (RHIString == TEXT("Vulkan"))
{
PXR_LOGI(PxrUnreal, "RHIString Vulkan");
RenderBridge = CreateRenderBridge_Vulkan(this);
}
else
#endif
{
#if PLATFORM_WINDOWS && WITH_EDITOR
return true;
#endif
PXR_LOGF(PxrUnreal, "%s is not currently supported by the PICOXR runtime", PLATFORM_CHAR(*RHIString));
return false;
}
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddRaw(this, &FPICOXRHMD::ApplicationPauseDelegate);
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddRaw(this, &FPICOXRHMD::ApplicationResumeDelegate);
if (!PreLoadLevelDelegate.IsValid())
{
PreLoadLevelDelegate = FCoreUObjectDelegates::PreLoadMap.AddRaw(this, &FPICOXRHMD::OnPreLoadMap);
}
IStereoLayers::FLayerDesc EyeLayerDesc;
EyeLayerDesc.Priority = INT_MIN;
EyeLayerDesc.Flags = LAYER_FLAG_TEX_CONTINUOUS_UPDATE;
const uint32 EyeLayerId = CreateLayer(EyeLayerDesc);
check(EyeLayerId == 0);
ContentResourceFinder = NewObject<UPICOContentResourceFinder>();
ContentResourceFinder->AddToRoot();
PICOSplash = MakeShareable(new FPXRSplash(this));
PICOSplash->InitSplash();
#ifdef PICO_CUSTOM_ENGINE
// Initialize Dynamic Resolution
GRHISupportsDynamicResolution = true;
GEngine->ChangeDynamicResolutionStateAtNextFrame(
MakeShareable(new FPXR_DynamicResolutionState(GameSettings, this))
);
#endif
#if PLATFORM_ANDROID
int CurrentVersion = 0;
FPICOXRVersionHelper::GetRuntimeAPIVersion(CurrentVersion);
if (CurrentVersion >= 0x2000306)
{
PXR_LOGI(LogMRC, "CurrentVersion:%d SetIsSupportMovingMrc to true!", CurrentVersion);
FPICOXRHMDModule::GetPluginWrapper().SetIsSupportMovingMrc(true);
}
#endif
return true;
}
void FPICOXRHMD::Shutdown()
{
CheckInGameThread();
if (PreLoadLevelDelegate.IsValid())
{
FCoreUObjectDelegates::PreLoadMap.Remove(PreLoadLevelDelegate);
PreLoadLevelDelegate.Reset();
}
if (ContentResourceFinder)
{
ContentResourceFinder->ConditionalBeginDestroy();
ContentResourceFinder = NULL;
}
if (PICOSplash.IsValid())
{
PICOSplash->ShutDownSplash();
PICOSplash = nullptr;
LoadingScreen = nullptr;
}
if (RenderBridge.IsValid())
{
RenderBridge->Shutdown();
RenderBridge = nullptr;
}
ReleaseDevice();
GameSettings.Reset();
PXRLayerMap.Reset();
}
void FPICOXRHMD::PollEvent()
{
PollFutureDelegate.Broadcast();
#if PLATFORM_ANDROID
int32 EventCount = 0;
PxrEventDataBuffer* EventData[PXR_MAX_EVENT_COUNT];
bool Ret = FPICOXRHMDModule::GetPluginWrapper().PollEvent(PXR_MAX_EVENT_COUNT, &EventCount, EventData);
if (Ret)
{
PXR_LOGD(PxrUnreal,"PollEvent EventCount :%d",EventCount);
ProcessEvent(EventCount, EventData);
}
#endif
}
void FPICOXRHMD::AllocateEyeLayer()
{
check(IsInGameThread());
ExecuteOnRenderThread([&]()
{
InitEyeLayer_RenderThread(GetImmediateCommandList_ForRenderCommand());
const FXRSwapChainPtr& SwapChain = PXREyeLayer_RenderThread->GetSwapChain();
if (SwapChain.IsValid())
{
const FRHITexture* const SwapChainTexture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
PXR_LOGI(PxrUnreal, "Allocating PICOXR %d x %d RenderTarget SwapChain!", SwapChainTexture->GetSizeX(), SwapChainTexture->GetSizeY());
}
});
bNeedReAllocateViewportRenderTarget = true;
}
FIntPoint FPICOXRHMD::GetRenderTargetSize() const
{
// First, start with the ideal render target size for this device
FIntPoint RenderTargetSize = GetIdealRenderTargetSize();
// Rescale render target based on maximum pixel density (if adaptive resolution enabled)
// or pixel density (if adaptive resolution disabled).
const float RenderTargetScale = GameSettings->IsAdaptiveResolutionEnabled() ? GameSettings->PixelDensityMax : GameSettings->PixelDensity;
if (!FMath::IsNearlyEqual(RenderTargetScale, 1.0f))
{
RenderTargetSize = (FVector2D(RenderTargetSize) * RenderTargetScale).IntPoint();
}
// Align render target size to 16x16 tiles.
// This fixes issues with buffer sizing.
RenderTargetSize = (RenderTargetSize / 16) * 16;
// Allocate double space for both eyes (if multiview disabled)
if (!bIsUsingMobileMultiView)
{
RenderTargetSize.X *= 2;
}
// Return if render target size not too big
const FIntPoint MaxRenderTargetSize = GetMaxRenderTargetSize();
if (RenderTargetSize.X < MaxRenderTargetSize.X && RenderTargetSize.Y < MaxRenderTargetSize.Y)
{
return RenderTargetSize;
}
// Otherwise, clamp to max
PXR_LOGW(PxrUnreal, "Requested RenderTargetSize(%d,%d) exceeds MaxRenderTargetSize(%d,%d)"
, RenderTargetSize.X
, RenderTargetSize.Y
, MaxRenderTargetSize.X
, MaxRenderTargetSize.Y
);
// Use component min to clamp (if multiview enabled)
// or overwrite with max render target size (if multiview disabled)
RenderTargetSize = (bIsUsingMobileMultiView ? RenderTargetSize.ComponentMin(MaxRenderTargetSize) : MaxRenderTargetSize);
return RenderTargetSize;
}
FIntPoint FPICOXRHMD::GetRenderViewportSize(const FIntPoint& RenderTargetSize) const
{
FIntPoint RenderViewportSize = RenderTargetSize;
const FIntPoint PrevRenderViewportSize = GameSettings->EyeRenderViewport[0].Size();
// If using double wide (i.e. not using mobile multiview),
// Then use only half the width
if (!bIsUsingMobileMultiView)
{
RenderViewportSize.X /= 2;
}
// Return if adaptive resolution is disabled
if (!GameSettings->IsAdaptiveResolutionEnabled())
{
return RenderViewportSize;
}
const float RenderViewportScale = GameSettings->PixelDensity / GameSettings->PixelDensityMax;
if (!FMath::IsNearlyEqual(RenderViewportScale, 1.0f))
{
RenderViewportSize = (FVector2D(RenderViewportSize) * RenderViewportScale).IntPoint();
}
// Align render target size to 16x16 tiles.
// This fixes issues with buffer sizing.
RenderViewportSize = ((RenderViewportSize + 8) / 16) * 16;
// Only resize by 32 pixels each frame
RenderViewportSize = RenderViewportSize.ComponentMax(PrevRenderViewportSize - 32);
RenderViewportSize = RenderViewportSize.ComponentMin(PrevRenderViewportSize + 32);
return RenderViewportSize;
}
void FPICOXRHMD::UpdateRenderTargetAndViewport()
{
// Update pixel density from console variable
static const auto PixelDensityCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("vr.PixelDensity"));
float PixelDensityRaw = (PixelDensityCVar ? PixelDensityCVar->GetFloat() : 1.0f);
PXR_LOGV(PxrUnreal, "PixelDensityRaw=%f", PixelDensityRaw);
// If adaptive resolution is enabled and vr.PixelDensity == 0, then get Adaptive Resolution value from Runtime
if (GameSettings->IsAdaptiveResolutionEnabled() && FMath::IsNearlyZero(PixelDensityRaw))
{
UpdateAdaptiveResolution(PixelDensityRaw);
}
else
{
GameSettings->SetPixelDensity(PixelDensityRaw);
}
// Get render target and render viewport sizes
const FIntPoint RenderTargetSize = GetRenderTargetSize();
const FIntPoint RenderViewportSize = GetRenderViewportSize(RenderTargetSize);
// Set render target and render viewport
GameSettings->RenderTargetSize = RenderTargetSize;
GameSettings->EyeRenderViewport[0] = GameSettings->EyeRenderViewport[1] = FIntRect(FIntPoint(0), RenderViewportSize);
// Offset right viewport (if multiview disabled)
if (!bIsUsingMobileMultiView)
{
FIntPoint Offset(RenderViewportSize.X, 0);
GameSettings->EyeRenderViewport[1] = FIntRect(Offset, RenderViewportSize + Offset);
}
GameSettings->EyeUnscaledRenderViewport[0] = GameSettings->EyeRenderViewport[0];
GameSettings->EyeUnscaledRenderViewport[1] = GameSettings->EyeRenderViewport[1];
PXR_LOGV(PxrUnreal, "RenderTargetSize:(%d,%d) and RenderViewportSize:(%d,%d)",
RenderTargetSize.X,
RenderTargetSize.Y,
RenderViewportSize.X,
RenderViewportSize.Y
);
PXR_LOGV(PxrUnreal, "LeftViewport:(%s) and RightViewport:(%s)",
*GameSettings->EyeRenderViewport[0].ToString(),
*GameSettings->EyeRenderViewport[1].ToString()
);
}
void FPICOXRHMD::UpdateAdaptiveResolution(float PixelDensityRaw)
{
PxrExtent2Di ViewportDimensions = { GameSettings->EyeRenderViewport[0].Width(), GameSettings->EyeRenderViewport[0].Height() };
PxrAdaptiveResolutionPowerSetting PowerSetting = {};
switch (GameSettings->AdaptiveResolutionPowerSetting)
{
case EPICOXRAdaptiveResolutionPowerSetting::BatterySaving:
PowerSetting = PXR_ADAPTIVE_RESOLUTION_BATTERY_SAVING;
break;
case EPICOXRAdaptiveResolutionPowerSetting::Balanced:
PowerSetting = PXR_ADAPTIVE_RESOLUTION_BALANCED;
break;
case EPICOXRAdaptiveResolutionPowerSetting::HighQuality:
PowerSetting = PXR_ADAPTIVE_RESOLUTION_HIGH_QUALITY;
break;
}
// Get Adaptive Resolution value from runtime
if (FPICOXRHMDModule::GetPluginWrapper().UpdateAdaptiveResolution(&ViewportDimensions, PowerSetting) != 0)
{
// Return if error
return;
}
FIntPoint DefaultDimensions = GetIdealRenderTargetSize();
PixelDensityRaw = float(ViewportDimensions.width) / float(DefaultDimensions.X);
GameSettings->SetPixelDensity(PixelDensityRaw);
}
void FPICOXRHMD::OnHomeKeyRecentered()
{
if (GetTrackingOrigin()!=EHMDTrackingOrigin::Type::Stage)
{
GameSettings->BaseOffset=FVector::ZeroVector;
GameSettings->BaseOrientation = FRotator(0, - GameSettings->CustomOffsetYaw, 0).Quaternion();
}
}
void FPICOXRHMD::Recenter(PxrRecenterTypes RecenterType, float Yaw)
{
CheckInGameThread();
if (NextGameFrameToRender_GameThread.IsValid())
{
PxrPosef RuntimePose={};
FPose UnrealPose=FPose(NextGameFrameToRender_GameThread->Orientation,NextGameFrameToRender_GameThread->Position);
ConvertPose_Internal(UnrealPose, RuntimePose, GameSettings.Get(), NextGameFrameToRender_GameThread->WorldToMetersScale);
if (RecenterType & RecenterPosition)
{
const bool floorLevel = GetTrackingOrigin() != EHMDTrackingOrigin::View;
GameSettings->BaseOffset = ToFVector(RuntimePose.position);
if (floorLevel)
GameSettings->BaseOffset.Z = 0;
}
if (RecenterType & RecenterOrientation)
{
GameSettings->CustomOffsetYaw=Yaw;
GameSettings->BaseOrientation = FRotator(0, FRotator(ToFQuat(RuntimePose.orientation)).Yaw - Yaw, 0).Quaternion();
}
UpdateSensorValue(GameSettings.Get(), NextGameFrameToRender_GameThread.Get());
}
}
void FPICOXRHMD::InitEyeLayer_RenderThread(FRHICommandListImmediate& RHICmdList)
{
check(!InGameThread());
CheckInRenderThread();
if (PXRLayerMap[0].IsValid())
{
FPICOLayerPtr EyeLayer = PXRLayerMap[0]->CloneMyself();
EyeLayer->InitPXRLayer_RenderThread(GameSettings_RenderThread.Get(), RenderBridge, &DelayDeletion, RHICmdList, PXREyeLayer_RenderThread.Get());
if (PXRLayers_RenderThread.Num() > 0)
{
PXRLayers_RenderThread[0] = EyeLayer;
}
else
{
PXRLayers_RenderThread.Add(EyeLayer);
}
#ifdef PICO_CUSTOM_ENGINE
if (EyeLayer->GetMotionVectorSwapChain().IsValid())
{
if (!PXREyeLayer_RenderThread.IsValid() || EyeLayer->GetMotionVectorSwapChain() != PXREyeLayer_RenderThread->GetMotionVectorSwapChain()
|| EyeLayer->GetMotionVectorDepthSwapChain() != PXREyeLayer_RenderThread->GetMotionVectorDepthSwapChain())
{
bNeedReAllocateMotionVectorTexture_RenderThread = true;
PXR_LOGI(PxrUnreal, "[Mobile SpaceWarp] request to re-allocate motionVector textures");
}
}
#endif
if (EyeLayer->GetFoveationSwapChain().IsValid())
{
if (!PXREyeLayer_RenderThread.IsValid() || EyeLayer->GetFoveationSwapChain() != PXREyeLayer_RenderThread->GetFoveationSwapChain())
{
bNeedReAllocateFoveationTexture_RenderThread = true;
}
FoveationImageGenerator = MakeUnique<FPICOXRFoveatedRenderingImageGenerator>(
EyeLayer->GetFoveationSwapChain());
}
DelayDeletion.AddLayerToDeferredDeletionQueue(PXREyeLayer_RenderThread);
PXREyeLayer_RenderThread = EyeLayer;
}
}
#ifdef PICO_CUSTOM_ENGINE
void FPICOXRHMD::UpdateFoveationOffsets_RenderThread()
{
if (RHIString != TEXT("Vulkan") ||
PICOXRSetting->FoveationRenderingMode != EFoveationRenderingMode::EyeTrackingFoveationRendering ||
PICOXRSetting->FoveationLevel == EFoveationLevel::Type::None ||
!PICOXRSetting->bEnableEyeTrackingFoveationRendering ||
!CVarPICOEnableEyeTrackedFoveatedRendering.GetValueOnRenderThread())
{
if (RenderBridge.IsValid())
{
PXR_LOGV(PxrUnreal, "FPICOXRHMD::PostRenderBasePass_RenderThread: UpdateFoveationOffsetsUsage_RHIThread Condition = false");
RenderBridge->UpdateFoveationOffsetsUsage_RHIThread(false);
}
return;
}
const FXRSwapChainPtr& EyeLayerSwapChain = PXREyeLayer_RenderThread.IsValid() ? PXREyeLayer_RenderThread->GetSwapChain() : nullptr;
if (!EyeLayerSwapChain.IsValid())
{
PXR_LOGV(PxrUnreal, "FPICOXRHMD::PostRenderBasePass_RenderThread: EyeLayerSwapChain is Invalid");
RenderBridge->UpdateFoveationOffsetsUsage_RHIThread(false);
return;
}
const FRHITexture2D* EyeLayerTexture = EyeLayerSwapChain->GetTexture2DArray() ? EyeLayerSwapChain->GetTexture2DArray() : EyeLayerSwapChain->GetTexture2D();
if (!EyeLayerTexture)
{
PXR_LOGV(PxrUnreal, "FPICOXRHMD::PostRenderBasePass_RenderThread: EyeLayerTexture is Invalid");
RenderBridge->UpdateFoveationOffsetsUsage_RHIThread(false);
return;
}
const FIntPoint EyeLayerTextureSize = EyeLayerTexture->GetSizeXY();
ExecuteOnRHIThread_DoNotWait([this, EyeLayerTextureSize]()
{
bool bUseOffset = false;
FIntPoint LeftOffset, RightOffset;
LeftOffset = RightOffset = FIntPoint::ZeroValue;
if (RenderBridge.IsValid())
{
RenderBridge->UpdateFoveationOffsetsUsage_RHIThread(true);
RenderBridge->UpdateFoveationOffsets_RHIThread(FIntPoint(-32,248), FIntPoint(-32,248));
FPICOXRHMDModule::GetPluginWrapper().SetEyeTrackingFoveationRenderingState(true);
}
bool bEnableEyeTrackingFoveationRenderingState = false;
if (FPICOXRHMDModule::GetPluginWrapper().GetEyeTrackingFoveationRenderingState(&bEnableEyeTrackingFoveationRenderingState) >= 0 &&
bEnableEyeTrackingFoveationRenderingState)
{
PXR_LOGV(PxrUnreal, "FPICOXRHMD::PostRenderBasePass_RenderThread: GetEyeTrackingFoveationRenderingState = true Success");
PxrVector2f CenterOffset[2];
PxrFoveationStateCode Result = (PxrFoveationStateCode)FPICOXRHMDModule::GetPluginWrapper().GetEyeTrackingFoveationRenderingCenter(CenterOffset);
if (PXRP_SUCCESS(Result))
{
bUseOffset = true;
LeftOffset.X = Align((int32)(EyeLayerTextureSize.X / 2 * CenterOffset[0].x), 8);
LeftOffset.Y = Align(0 - (int32)(EyeLayerTextureSize.Y / 2 * CenterOffset[0].y), 8);
RightOffset.X = Align((int32)(EyeLayerTextureSize.X / 2 * CenterOffset[1].x), 8);
RightOffset.Y = Align(0 - (int32)(EyeLayerTextureSize.Y / 2 * CenterOffset[1].y), 8);
PXR_LOGV(PxrUnreal, "FPICOXRHMD::PostRenderBasePass_RenderThread: GetEyeTrackingFoveationRenderingCenter Success: bUseOffset[%d], TargetLeft[%s], TargetRight[%s]",
bUseOffset ? 1 : 0, *LeftOffset.ToString(), *RightOffset.ToString());
}
else if (Result != PxrFoveationStateCode::PXR_FOVEATION_InvalidData)
{
PICOXRSetting->FoveationRenderingMode = EFoveationRenderingMode::FixedFoveationRendering;
PICOXRSetting->bEnableEyeTrackingFoveationRendering = false;
FPICOXRHMDModule::GetPluginWrapper().SetEyeTrackingFoveationRenderingState(false);
PXR_LOGI(PxrUnreal, "FPICOXRHMD::PostRenderBasePass_RenderThread: GetEyeTrackingFoveationRenderingCenter != PXR_FOVEATION_InvalidData Failed");
}
else
{
PXR_LOGV(PxrUnreal, "FPICOXRHMD::PostRenderBasePass_RenderThread: GetEyeTrackingFoveationRenderingCenter == PXR_FOVEATION_InvalidData Failed");
}
}
if (RenderBridge.IsValid())
{
RenderBridge->UpdateFoveationOffsetsUsage_RHIThread(bUseOffset);
RenderBridge->UpdateFoveationOffsets_RHIThread(LeftOffset, RightOffset);
}
});
}
#endif
void FPICOXRHMD::ProcessEvent(int32 EventCount, PxrEventDataBuffer** EventData)
{
if (EventCount ==0 || !EventData)
{
return;
}
for (int i = 0; i < EventCount; i++)
{
PxrEventDataBuffer* Event = EventData[i];
PXR_LOGD(PxrUnreal,"ProcessEvent EventCount:%d,EventType[%d]:%d",EventCount,i,Event->type);
switch(Event->type)
{
case PXR_TYPE_EVENT_DATA_SESSION_STATE_READY:
{
PXR_LOGI(PxrUnreal, "Session Ready!");
BeginXR();
break;
}
case PXR_TYPE_EVENT_DATA_SESSION_STATE_STOPPING:
{
PXR_LOGI(PxrUnreal, "Session Stopping!");
EndXR();
break;
}
case PXR_TYPE_EVENT_DATA_SEETHROUGH_STATE_CHANGED:
{
const PxrEventDataSeethroughStateChanged SeeThroughData = *reinterpret_cast<const PxrEventDataSeethroughStateChanged*>(Event);
OnSeeThroughStateChange(SeeThroughData.state);
break;
}
case PXR_TYPE_EVENT_FOVEATION_LEVEL_CHANGED:
{
const PxrEventDataFoveationLevelChanged FoveationData = *reinterpret_cast<const PxrEventDataFoveationLevelChanged*>(Event);
OnFoveationLevelChange(FoveationData.level);
break;
}
case PXR_TYPE_EVENT_FRUSTUM_STATE_CHANGED:
{
const PxrEventDataFrustumChanged FrustumData = *reinterpret_cast<const PxrEventDataFrustumChanged*>(Event);
OnFrustumStateChange();
break;
}
case PXR_TYPE_EVENT_RENDER_TEXTURE_CHANGED:
{
const PxrEventDataRenderTextureChanged RenderTextureChanged = *reinterpret_cast<const PxrEventDataRenderTextureChanged*>(Event);
OnRenderTextureChange(RenderTextureChanged.width,RenderTextureChanged.height);
break;
}
case PXR_TYPE_EVENT_TARGET_FRAME_RATE_STATE_CHANGED:
{
const PxrEventDataTargetFrameRateChanged FrameRateChanged = *reinterpret_cast<const PxrEventDataTargetFrameRateChanged*>(Event);
OnTargetFrameRateChange(FrameRateChanged.frameRate);
break;
}
case PXR_TYPE_EVENT_DATA_CONTROLLER:
{
const PxrEventDataControllerChanged Controller = *reinterpret_cast<const PxrEventDataControllerChanged*>(Event);
ProcessControllerEvent(Controller);
break;
}
case PXR_TYPE_EVENT_HARDIPD_STATE_CHANGED:
{
const PxrEventDataHardIPDStateChanged IPDState = *reinterpret_cast<const PxrEventDataHardIPDStateChanged*>(Event);
IpdValue = IPDState.ipd;
PXR_LOGD(PxrUnreal,"ProcessEvent PXR_TYPE_EVENT_HARDIPD_STATE_CHANGED IPD:%f",IPDState.ipd);
EventManager->IpdChangedDelegate.Broadcast(IPDState.ipd);
UPICOXRHMDFunctionLibrary::PICOXRIPDChangedCallback.ExecuteIfBound(IPDState.ipd);
break;
}
case PXR_TYPE_EVENT_DATA_HMD_KEY:
{
const PxrEventDataHmdKey HomeKey = *reinterpret_cast<const PxrEventDataHmdKey*>(Event);
EventManager->LongHomePressedDelegate.Broadcast();
EventManager->RawLongHomePressedDelegate.Broadcast();
if (FCoreDelegates::VRHeadsetRecenter.IsBound())
{
FCoreDelegates::VRHeadsetRecenter.Broadcast();
}
break;
}
case PXR_TYPE_EVENT_DATA_MRC_STATUS:
{
const PxrEventDataMrcStatusChanged MRC = *reinterpret_cast<const PxrEventDataMrcStatusChanged*>(Event);
MRCEnabled = MRC.mrc_status == 0 ? true : false;
break;
}
case PXR_TYPE_EVENT_DATA_REFRESH_RATE_CHANGED:
{
const PxrEventDataRefreshRateChanged RateState = *reinterpret_cast<const PxrEventDataRefreshRateChanged*>(Event);
PXR_LOGD(PxrUnreal, "ProcessEvent PXR_TYPE_EVENT_DATA_REFRESH_RATE_CHANGED Rate:%f", RateState.refrashRate);
DisplayRefreshRate = RateState.refrashRate;
EventManager->RefreshRateChangedDelegate.Broadcast(RateState.refrashRate);
break;
}
case PXR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
{
const PxrEventDataSessionStateChanged sessionStateChanged = *reinterpret_cast<const PxrEventDataSessionStateChanged*>(Event);
inputFocusState = sessionStateChanged.state == PXR_SESSION_STATE_FOCUSED;
break;
}
case PXR_TYPE_EVENT_HMD_BATTERY_CHANGED:
{
const PxrEventHmdBatteryChanged BatteryStateChanged = *reinterpret_cast<const PxrEventHmdBatteryChanged*>(Event);
PXR_LOGD(PxrUnreal, "ProcessEvent PXR_TYPE_EVENT_HMD_BATTERY_CHANGED BatteryState:%d", BatteryStateChanged.value);
EventManager->BatteryStateChangedDelegate.Broadcast(BatteryStateChanged.value);
CurrentHMDBatteryLevel=BatteryStateChanged.value;
break;
}
case PXR_TYPE_EVENT_MOTION_TRACKER_KEY_EVENT:
{
const PxrEventDataMotionTrackerKey MotionTrackerKeyEvent = *reinterpret_cast<const PxrEventDataMotionTrackerKey*>(Event);
FPXREventDataMotionTrackerKey EventDataMotionTrackerKey={};
EventDataMotionTrackerKey.TrackerSN = FString(UTF8_TO_TCHAR(MotionTrackerKeyEvent.trackerSN));
EventDataMotionTrackerKey.Code =MotionTrackerKeyEvent.code;
EventDataMotionTrackerKey.Action =MotionTrackerKeyEvent.action;
EventDataMotionTrackerKey.Repeat=MotionTrackerKeyEvent.repeat;
EventDataMotionTrackerKey.bShortPress =MotionTrackerKeyEvent.shortPress;
PXR_LOGD(PxrUnreal, "ProcessEvent PXR_TYPE_EVENT_MOTION_TRACKER_KEY_EVENT Code:%d,action:%d,repeat:%d,shortPress:%d"
,MotionTrackerKeyEvent.code
,MotionTrackerKeyEvent.action
,MotionTrackerKeyEvent.repeat
,MotionTrackerKeyEvent.shortPress);
EventManager->DataMotionTrackerKeyDelegate.Broadcast(EventDataMotionTrackerKey);
break;
}
case PXR_TYPE_EVENT_EXT_DEV_CONNECT_STATE_EVENT:
{
const PxrEventDataExtDevConnectEvent ExtDevConnectEvent = *reinterpret_cast<const PxrEventDataExtDevConnectEvent*>(Event);
FPXREventDataExtDevConnectEvent EventDataExtDevConnectEvent={};
EventDataExtDevConnectEvent.TrackerSN = FString(UTF8_TO_TCHAR(ExtDevConnectEvent.trackerSN));
EventDataExtDevConnectEvent.state =ExtDevConnectEvent.state;
PXR_LOGD(PxrUnreal, "ProcessEvent PXR_TYPE_EVENT_EXT_DEV_CONNECT_STATE_EVENT TrackerSN:%s,state:%d", *EventDataExtDevConnectEvent.TrackerSN
, EventDataExtDevConnectEvent.state);
EventManager->DataExtDevConnectEventDelegate.Broadcast(EventDataExtDevConnectEvent);
break;
}
case PXR_TYPE_EVENT_EXT_DEV_BATTERY_STATE_EVENT:
{
const PxrEventDataExtDevBatteryEvent ExtDevBatteryEvent = *reinterpret_cast<const PxrEventDataExtDevBatteryEvent*>(Event);
FPXREventDataExtDevBatteryEvent EventDataExtDevBatteryEvent={};
PXR_LOGD(PxrUnreal, "ProcessEvent PXR_TYPE_EVENT_EXT_DEV_BATTERY_STATE_EVENT TrackerSN:%s,battery:%d,charger:%d", *EventDataExtDevBatteryEvent.TrackerSN
, EventDataExtDevBatteryEvent.battery
, EventDataExtDevBatteryEvent.charger);
EventManager->DataExtDevBatteryEventDelegate.Broadcast(EventDataExtDevBatteryEvent);
break;
}
case PXR_TYPE_EVENT_MOTION_TRACKING_MODE_CHANGED_EVENT:
{
const PxrEventDataMotionTrackingModeChangedEvent DataMotionTrackingModeChangedEvent = *reinterpret_cast<const PxrEventDataMotionTrackingModeChangedEvent*>(Event);
PXR_LOGD(PxrUnreal, "ProcessEvent PXR_TYPE_EVENT_MOTION_TRACKING_MODE_CHANGED_EVENT Mode:%d", DataMotionTrackingModeChangedEvent.mode);
EventManager->DataMotionTrackingModeChangedEventDelegate.Broadcast(DataMotionTrackingModeChangedEvent.mode);
break;
}
case PXR_TYPE_EVENT_EXT_DEV_PASS_DATA_EVENT:
{
const PxrEventDataExtDevPassDataEvent DataExtDevPassDataEvent = *reinterpret_cast<const PxrEventDataExtDevPassDataEvent*>(Event);
PXR_LOGD(PxrUnreal, "ProcessEvent PXR_TYPE_EVENT_EXT_DEV_PASS_DATA_EVENT status:%d", DataExtDevPassDataEvent.status);
EventManager->DataExtDevPassDataEventDelegate.Broadcast(DataExtDevPassDataEvent.status);
break;
}
case PXR_TYPE_EVENT_VST_DISPLAY_STATUS_CHANGED:
{
const PxrEventDataVstDisplayChanged DataVstDisplayChanged = *reinterpret_cast<const PxrEventDataVstDisplayChanged*>(Event);
PXR_LOGD(PxrUnreal, "ProcessEvent PXR_TYPE_EVENT_VST_DISPLAY_STATUS_CHANGED displayStatus:%d", DataVstDisplayChanged.displayStatus);
EventManager->VSTDisplayChangedDelegate.Broadcast(static_cast<EPICOVSTDisplayStatus>(DataVstDisplayChanged.displayStatus));
break;
}
default:
{
PollEventDelegate.Broadcast(Event);
break;
}
}
}
}
void FPICOXRHMD::ProcessControllerEvent(const PxrEventDataControllerChanged EventData)
{
#if PLATFORM_ANDROID
PXR_LOGD(PxrUnreal,"ProcessControllerEvent eventtype:%d,Handness:%d,State:%d",EventData.eventtype,EventData.controller,EventData.status);
FPICOXRInput* PICOInput = GetPICOXRInput();
switch (EventData.eventtype)
{
case PXR_DEVICE_CONNECTCHANGED:
{
PXR_LOGD(PxrUnreal,"ProcessControllerEvent PXR_DEVICE_CONNECTCHANGED controller:%d,State:%d",EventData.controller,EventData.status);
PICOInput->OnControllerConnectChangedDelegate(EventData.controller, EventData.status);
EventManager->DeviceConnectChangedDelegate.Broadcast(EventData.controller, EventData.status);
break;
}
case PXR_DEVICE_MAIN_CHANGED:
{
PXR_LOGD(PxrUnreal, "ProcessControllerEvent PXR_DEVICE_MAIN_CHANGED controller:%d", EventData.controller);
PICOInput->OnControllerMainChangedDelegate(EventData.controller);
EventManager->DeviceMainChangedDelegate.Broadcast(EventData.controller);
break;
}
case PXR_DEVICE_INPUTDEVICE_CHANGED :
{
PXR_LOGD(PxrUnreal, "ProcessControllerEvent PXR_DEVICE_INPUTDEVICE_CHANGED status:%d", EventData.status);
EventManager->InputDeviceChangedDelegate.Broadcast(EventData.status);
break;
}
case PXR_DEVICE_HANDNESS_CHANGED:
{
PXR_LOGD(PxrUnreal, "ProcessControllerEvent PXR_DEVICE_HANDNESS_CHANGED controller:%d", EventData.controller);
EventManager->HandnessChangedDelegate.Broadcast(EventData.controller);
break;
}
case PXR_DEVICE_FITNESSBAND_STATE:
{
PXR_LOGD(PxrUnreal, "PXR PXR_DEVICE_FITNESSBAND_STATE Count:%d Status:%d",EventData.varying[0],EventData.status);
EventData.status==2?(EventManager->MotionTrackerRecalibrationDelegate.Broadcast()):(EventManager->MotionTrackerConnectionDelegate.Broadcast(EventData.varying[0],EventData.status));
break;
}
case PXR_DEVICE_FITNESSBAND_BATTERY:
{
PXR_LOGD(PxrUnreal, "PXR PXR_DEVICE_FITNESSBAND_BATTERY ID:%d batteryStatus:%d",EventData.status,EventData.varying[0]);
EventManager->MotionTrackerBatteryDelegate.Broadcast(EventData.status,EventData.varying[0]);
break;
}
case PXR_DEVICE_BODYTRACKING_STATE_ERROR_CODE:
{
PXR_LOGD(PxrUnreal, "PXR PXR_DEVICE_BODYTRACKING_STATE_ERROR_CODE Status:%d ErrorCode:%d",EventData.status,EventData.varying[0]);
EventManager->BodyTrackingStateErrorDelegate.Broadcast(EventData.status,EventData.varying[0]);
break;
}
case PXR_DEVICE_BODYTRACKING_ACTION:
{
PXR_LOGD(PxrUnreal, "PXR PXR_DEVICE_BODYTRACKING_ACTION Status:%d Action:%d",EventData.status,EventData.varying[0]);
int Action = EventData.varying[0];
EPxrBodyActionList BodyAction=EPxrBodyActionList::NoneAction;
if ((Action & PxrBodyActionList::PxrTouchGround) && (Action & PxrBodyActionList::PxrKeepStatic))
{
BodyAction = EPxrBodyActionList::TouchGroundAndKeepStatic;
}
else if (Action & PxrBodyActionList::PxrFootDownAction)
{
BodyAction = EPxrBodyActionList::FootDownAction;
}
else if (Action & PxrBodyActionList::PxrTouchGround)
{
BodyAction = EPxrBodyActionList::TouchGround;
}
else if (Action & PxrBodyActionList::PxrKeepStatic)
{
BodyAction = EPxrBodyActionList::KeepStatic;
}
else if (Action & PxrBodyActionList::PxrTouchGroundToe)
{
BodyAction = EPxrBodyActionList::TouchGroundToe;
}
else
{
BodyAction = EPxrBodyActionList::NoneAction;
}
EventManager->BodyTrackingActionDelegate.Broadcast(EventData.status,BodyAction);
break;
}
default:
break;
}
#endif
}
void FPICOXRHMD::MakeAllStereolayerComponentsUpdate()
{
TArray<UObject*> StereoLayerComponents;
GetObjectsOfClass(UStereoLayerComponent::StaticClass(), StereoLayerComponents);
for (int32 StereoLayerComponentIndex = 0; StereoLayerComponentIndex < StereoLayerComponents.Num(); ++StereoLayerComponentIndex)
{
UStereoLayerComponent* StereoLayerComponent = Cast<UStereoLayerComponent>(StereoLayerComponents[StereoLayerComponentIndex]);
check(StereoLayerComponent);
StereoLayerComponent->MarkTextureForUpdate();
}
PXR_LOGD(PxrUnreal, "Layer Create InLayerDesc.Texture Failed!!!!!");
}
void FPICOXRHMD::MakeAllStereoLayerComponentsDirty()
{
TArray<UObject*> StereoLayerComponents;
GetObjectsOfClass(UStereoLayerComponent::StaticClass(), StereoLayerComponents);
for (int32 StereoLayerComponentIndex = 0; StereoLayerComponentIndex < StereoLayerComponents.Num(); ++StereoLayerComponentIndex)
{
UStereoLayerComponent* StereoLayerComponent = Cast<UStereoLayerComponent>(StereoLayerComponents[StereoLayerComponentIndex]);
check(StereoLayerComponent);
StereoLayerComponent->MarkStereoLayerDirty();
}
}
void FPICOXRHMD::OnSeeThroughStateChange(int SeeThroughState)
{
PXR_LOGD(PxrUnreal, "OnSeeThroughStateChange SeeThroughState:%d", SeeThroughState);
bSeeThroughIsShown = SeeThroughState ? true : false;
if (SeeThroughState == 2)
{
bNeedDrawBlackEye = true;
}
else
{
bNeedDrawBlackEye = false;
}
CheckInGameThread();
check(GameSettings.IsValid());
GameSettings->SeeThroughState = SeeThroughState;
}
void FPICOXRHMD::OnFoveationLevelChange(int32 NewFoveationLevel)
{
GameSettings->FoveatedRenderingLevel = static_cast<PxrFoveationLevel>(NewFoveationLevel);
#if PLATFORM_ANDROID
FPICOXRHMDModule::GetPluginWrapper().SetFoveationLevel(GameSettings->FoveatedRenderingLevel);
#endif
}
void FPICOXRHMD::OnFrustumStateChange()
{
#if PLATFORM_ANDROID
FPICOXRHMDModule::GetPluginWrapper().GetFrustum(PXR_EYE_LEFT, &LeftFrustum.FovLeft, &LeftFrustum.FovRight, &LeftFrustum.FovUp, &LeftFrustum.FovDown,&LeftFrustum.Near,&LeftFrustum.Far);
FPICOXRHMDModule::GetPluginWrapper().GetFrustum(PXR_EYE_RIGHT, &RightFrustum.FovLeft, &RightFrustum.FovRight, &RightFrustum.FovUp, &RightFrustum.FovDown,&RightFrustum.Near,&RightFrustum.Far);
LeftFrustum.FovLeft = FMath::Atan(LeftFrustum.FovLeft/LeftFrustum.Near);
LeftFrustum.FovRight = FMath::Atan(LeftFrustum.FovRight/LeftFrustum.Near);
LeftFrustum.FovUp = FMath::Atan(LeftFrustum.FovUp/LeftFrustum.Near);
LeftFrustum.FovDown = FMath::Atan(LeftFrustum.FovDown/LeftFrustum.Near);
RightFrustum.FovLeft = FMath::Atan(RightFrustum.FovLeft/RightFrustum.Near);
RightFrustum.FovRight = FMath::Atan(RightFrustum.FovRight/RightFrustum.Near);
RightFrustum.FovUp = FMath::Atan(RightFrustum.FovUp/RightFrustum.Near);
RightFrustum.FovDown = FMath::Atan(RightFrustum.FovDown/RightFrustum.Near);
#endif
}
void FPICOXRHMD::OnRenderTextureChange(int32 Width, int32 Height)
{
PXR_LOGI(PxrUnreal, "OnRenderTextureChange RenderTextureSize:(%d,%d)", IdealRenderTargetSize.X, IdealRenderTargetSize.Y);
}
void FPICOXRHMD::OnTargetFrameRateChange(int32 NewFrameRate)
{
GEngine->SetMaxFPS(NewFrameRate);
}
bool FPICOXRHMD::InitializeSession()
{
#if PLATFORM_ANDROID
if (FPICOXRHMDModule::GetPluginWrapper().IsInitialized() && !FPICOXRHMDModule::GetPluginWrapper().bIsSessionInitialized)
{
#if PLATFORM_ANDROID
if (RenderBridge.IsValid())
{
if (RHIString == TEXT("Vulkan"))
{
RenderBridge->GetGraphics();
}
FPICOXRHMDModule::GetPluginWrapper().bIsSessionInitialized = true;
}
#endif
PXR_LOGI(PxrUnreal, "InitializeSession OK!");
}
{
//SetAppEngineInfo2
FString UnrealSDKVersion = "UE5_3.2.3";
FString UnrealVersion = FString::FromInt(ENGINE_MINOR_VERSION);
UnrealSDKVersion = UnrealSDKVersion + UnrealVersion;
PXR_LOGI(PxrUnreal, "%s,xrVersion:%s", PLATFORM_CHAR(*FEngineVersion::Current().ToString()), PLATFORM_CHAR(*UnrealSDKVersion));
FPICOXRHMDModule::GetPluginWrapper().SetConfigString(PXR_ENGINE_VERSION, TCHAR_TO_UTF8(*UnrealSDKVersion));
}
//Config MobileMultiView
GSupportsMobileMultiView = FPICOXRHMDModule::GetPluginWrapper().GetFeatureSupported(PXR_FEATURE_MULTIVIEW);
{
//ffr
PXR_LOGI(PxrUnreal, "FoveationLevel=%d", GameSettings->FoveatedRenderingLevel);
FPICOXRHMDModule::GetPluginWrapper().SetFoveationLevel(GameSettings->FoveatedRenderingLevel);
}
{
//Config about OpenGL Context NoError
// #if PICO_HMD_SUPPORTED_PLATFORMS_OPENGL && USE_ANDROID_EGL_NO_ERROR_CONTEXT
// bool bUseNoErrorContext = true;
// bUseNoErrorContext = AndroidEGL::GetInstance()->GetSupportsNoErrorContext();
// PXR_LOGI(PxrUnreal, "bUseNoErrorContext = %d", bUseNoErrorContext);
// FPICOXRHMDModule::GetPluginWrapper().SetConfigInt(PXR_UNREAL_OPENGL_NOERROR, bUseNoErrorContext ? 1 : 0);
// #endif
}
{
//waitframe version call WaitFrame();
bWaitFrameVersion = FPICOXRVersionHelper::IsThisVersionOrGreater(0x2000304)? true : false;
if (FPICOXRVersionHelper::IsThisVersionOrGreater(0x2000305))
{
FString TempString;
int Value = 1024;
if (GConfig->GetString(FPlatformProperties::GetRuntimeSettingsClassName(), TEXT("AudioCallbackBufferFrameSize"), TempString, GEngineIni))
{
Value = FCString::Atoi(*TempString);
PXR_LOGI(PxrUnreal, "AudioCallbackBufferFrameSize = %d", Value);
}
int level = 0;
if (Value <= 768)
{
level = 2;
}
else if (Value <= 1536)
{
level = 3;
}
else
{
level = 4;
}
PXR_LOGI(PxrUnreal, "AudioCallbackBufferFrameSize = %d", Value);
FPICOXRHMDModule::GetPluginWrapper().SetControllerDelay(level);
}
}
//Config MobileMSAA
{
//Clamp MSAA to MAX Support
static const auto CVarMobileMSAA = IConsoleManager::Get().FindConsoleVariable(TEXT("r.MSAACount"));
if (CVarMobileMSAA && PICOXRSetting && RenderBridge)
{
int MobileMSAAValue = PICOXRSetting->bUseRecommendedMSAA ? RenderBridge->GetSystemRecommendedMSAA() : CVarMobileMSAA->GetInt();
// #if PICO_HMD_SUPPORTED_PLATFORMS_OPENGL
// if (RHIString == TEXT("OpenGL"))
// {
// int32 MaxMSAASamplesSupported = FOpenGL::GetMaxMSAASamplesTileMem();
// MobileMSAAValue = MobileMSAAValue > MaxMSAASamplesSupported ? MaxMSAASamplesSupported : MobileMSAAValue;
// }
// #endif
CVarMobileMSAA->Set(MobileMSAAValue);
PXR_LOGI(PxrUnreal, "Final MSAA = %d", MobileMSAAValue);
}
else
{
PXR_LOGI(PxrUnreal, "Config MSAA Level Failed");
}
}
PICOFlags.NeedSetTrackingOrigin = true;
NextGameFrameNumber = 1;
WaitedFrameNumber = 0;
FPICOXRHMDModule::GetPluginWrapper().SetColorSpace(IsMobileColorsRGB() ? PXR_COLOR_SPACE_SRGB : PXR_COLOR_SPACE_LINEAR);
#endif
return true;
}
void FPICOXRHMD::DoSessionShutdown()
{
PXR_LOGI(PxrUnreal, "DoSessionShutdown");
ExecuteOnRenderThread([this]()
{
ExecuteOnRHIThread([this]()
{
for (int32 LayerIndex = 0; LayerIndex < PXRLayers_RenderThread.Num(); LayerIndex++)
{
PXRLayers_RenderThread[LayerIndex]->ReleaseResources_RHIThread();
}
for (int32 LayerIndex = 0; LayerIndex < PXRLayers_RHIThread.Num(); LayerIndex++)
{
PXRLayers_RHIThread[LayerIndex]->ReleaseResources_RHIThread();
}
if (PICOSplash.IsValid())
{
PICOSplash->ReleaseResources_RHIThread();
}
if (RenderBridge)
{
RenderBridge->ReleaseResources_RHIThread();
}
GameSettings_RHIThread.Reset();
GameFrame_RHIThread.Reset();
PXRLayers_RHIThread.Reset();
});
GameSettings_RenderThread.Reset();
GameFrame_GameThread.Reset();
PXRLayers_RenderThread.Reset();
PXREyeLayer_RenderThread.Reset();
DelayDeletion.HandleLayerDeferredDeletionQueue_RenderThread(true);
});
GameFrame_GameThread.Reset();
NextGameFrameToRender_GameThread.Reset();
LastGameFrameToRender_GameThread.Reset();
// The Editor may release VR focus in OnEndPlay
if (!GIsEditor)
{
FApp::SetUseVRFocus(false);
FApp::SetHasVRFocus(false);
}
ShutdownSession();
}
void FPICOXRHMD::ShutdownSession()
{
#if PLATFORM_ANDROID
FPICOXRHMDModule::GetPluginWrapper().bIsSessionInitialized = false;
FPICOXRHMDModule::GetPluginWrapper().Shutdown();
#endif
bIsRendering_RenderThread = false;
}
bool FPICOXRHMD::InitDevice()
{
CheckInGameThread();
#if PLATFORM_ANDROID
if (FPICOXRHMDModule::GetPluginWrapper().bIsSessionInitialized)
{
return true;
}
if (!IsHMDEnabled())
{
return false;
}
LoadFromSettings();
if (!InitializeSession())
{
PXR_LOGI(PxrUnreal, "HMD InitializeSession failed!");
return false;
}
FPICOXRHMDModule::GetPluginWrapper().GetConfigInt(PXR_RENDER_TEXTURE_WIDTH, &IdealRenderTargetSize.X);
FPICOXRHMDModule::GetPluginWrapper().GetConfigInt(PXR_RENDER_TEXTURE_HEIGHT, &IdealRenderTargetSize.Y);
bNeedReAllocateViewportRenderTarget = true;
bNeedReAllocateFoveationTexture_RenderThread = false;
#ifdef PICO_CUSTOM_ENGINE
bool bSupportFoveationRendering = false;
FPICOXRHMDModule::GetPluginWrapper().GetEyeTrackingFoveationRenderingSupported(&bSupportFoveationRendering);
bool bEnableFoveationRendering = bSupportFoveationRendering && PICOXRSetting->bEnableEyeTrackingFoveationRendering && PICOXRSetting->FoveationRenderingMode == EFoveationRenderingMode::EyeTrackingFoveationRendering;
if (bEnableFoveationRendering)
{
FPICOXRHMDModule::GetPluginWrapper().SetEyeTrackingFoveationRenderingState(true);
}
#endif
UpdateStereoRenderingParams();
ExecuteOnRenderThread([this](FRHICommandListImmediate& RHICmdList)
{
InitEyeLayer_RenderThread(RHICmdList);
});
if (!PXREyeLayer_RenderThread.IsValid() || !PXREyeLayer_RenderThread->GetSwapChain().IsValid())
{
PXR_LOGE(PxrUnreal, "Failed to create eye layer swap chain.");
ShutdownSession();
return false;
}
if (!GIsEditor)
{
FApp::SetUseVRFocus(true);
FApp::SetHasVRFocus(true);
}
EnableContentProtect(PICOXRSetting->bUseContentProtect);
FPICOXRHMDModule::GetPluginWrapper().SetColorSpace(IsMobileColorsRGB() ? PXR_COLOR_SPACE_SRGB : PXR_COLOR_SPACE_LINEAR);
if (PICOXRSetting && PICOXRSetting->bEnableEyeTracking)
{
FPICOXRHMDModule::GetPluginWrapper().WantEyeTrackingService();
}
if (PICOXRSetting && PICOXRSetting->bEnableFaceTracking)
{
FPICOXRHMDModule::GetPluginWrapper().WantFaceTrackingService();
}
if (PICOXRSetting && PICOXRSetting->bEnableBodyTracking)
{
FPICOXRHMDModule::GetPluginWrapper().WantBodyTrackingService();
}
#endif
return true;
}
void FPICOXRHMD::ReleaseDevice()
{
CheckInGameThread();
#if PLATFORM_ANDROID
if (FPICOXRHMDModule::GetPluginWrapper().bIsSessionInitialized)
{
bShutdownRequestQueued = true;
}
#endif
}
void FPICOXRHMD::LoadFromSettings()
{
UPICOXRSettings* HMDSettings = GetMutableDefault<UPICOXRSettings>();
check(HMDSettings);
GameSettings->FoveatedRenderingLevel = static_cast<PxrFoveationLevel>(int(HMDSettings->FoveationLevel.GetValue()) - 1);
GameSettings->bLateLatching = HMDSettings->bEnableLateLatching;
GameSettings->CoordinateType = HMDSettings->CoordinateType;
GameSettings->PixelDensityMin = FMath::Min(HMDSettings->PixelDensityMin, HMDSettings->PixelDensityMax);
GameSettings->PixelDensityMax = FMath::Max(HMDSettings->PixelDensityMin, HMDSettings->PixelDensityMax);
GameSettings->bAdaptiveResolutionEnabled = HMDSettings->bAdaptiveResolutionEnabled;
GameSettings->AdaptiveResolutionPowerSetting = HMDSettings->AdaptiveResolutionPowerSetting;
}
void FPICOXRHMD::ApplicationPauseDelegate()
{
PXR_LOGI(PxrUnreal,"FPICOXRHMD::ApplicationPauseDelegate");
PICOFlags.AppIsPaused = true;
}
void FPICOXRHMD::ApplicationResumeDelegate()
{
PXR_LOGI(PxrUnreal, "FPICOXRHMD::ApplicationResumeDelegate");
if (EventManager)
{
EventManager->ResumeDelegate.Broadcast();
}
if (PICOFlags.AppIsPaused && !InitializeSession())
{
PXR_LOGI(PxrUnreal, "HMD InitializeSession failed!!!");
}
PICOFlags.AppIsPaused = false;
#ifdef PICO_CUSTOM_ENGINE
bool bSupportFoveationRendering = false;
FPICOXRHMDModule::GetPluginWrapper().GetEyeTrackingFoveationRenderingSupported(&bSupportFoveationRendering);
bool bEnableFoveationRendering = bSupportFoveationRendering && PICOXRSetting->bEnableEyeTrackingFoveationRendering && PICOXRSetting->FoveationRenderingMode == EFoveationRenderingMode::EyeTrackingFoveationRendering;
if (bEnableFoveationRendering)
{
FPICOXRHMDModule::GetPluginWrapper().SetEyeTrackingFoveationRenderingState(true);
}
#endif
}
void FPICOXRHMD::UpdateSensorValue(const FGameSettings* InSettings, FPXRGameFrame* InFrame)
{
#if PLATFORM_ANDROID
int32 ViewNumber = 0;
int eyeCount = 1;
PxrPosef PoseNoUse;
FPose Pose;
switch (InSettings->CoordinateType)
{
case EPICOXRCoordinateType::Local:
{
PxrSensorState SensorState;
FPICOXRHMDModule::GetPluginWrapper().GetPredictedMainSensorStateWithEyePose(InFrame->predictedDisplayTimeMs, &SensorState, &ViewNumber, eyeCount, &PoseNoUse);
InFrame->Acceleration = ToFVector(SensorState.linearAcceleration);
InFrame->AngularAcceleration = ToFVector(SensorState.angularAcceleration);
InFrame->AngularVelocity = ToFVector(SensorState.angularVelocity);
InFrame->Velocity = ToFVector(SensorState.linearVelocity);
ConvertPose_Internal(SensorState.pose, Pose, InSettings, InFrame->WorldToMetersScale);
}
break;
case EPICOXRCoordinateType::Global_BoundarySystem:
{
PxrSensorState2 SensorState2;
FPICOXRHMDModule::GetPluginWrapper().GetPredictedMainSensorState2(InFrame->predictedDisplayTimeMs, &SensorState2, &ViewNumber);
InFrame->Acceleration = ToFVector(SensorState2.linearAcceleration);
InFrame->AngularAcceleration = ToFVector(SensorState2.angularAcceleration);
InFrame->AngularVelocity = ToFVector(SensorState2.angularVelocity);
InFrame->Velocity = ToFVector(SensorState2.linearVelocity);
ConvertPose_Internal(SensorState2.globalPose, Pose, InSettings, InFrame->WorldToMetersScale);
}
break;
default:
{
PxrSensorState SensorState;
FPICOXRHMDModule::GetPluginWrapper().GetPredictedMainSensorStateWithEyePose(InFrame->predictedDisplayTimeMs, &SensorState, &ViewNumber, eyeCount, &PoseNoUse);
InFrame->Acceleration = ToFVector(SensorState.linearAcceleration);
InFrame->AngularAcceleration = ToFVector(SensorState.angularAcceleration);
InFrame->AngularVelocity = ToFVector(SensorState.angularVelocity);
InFrame->Velocity = ToFVector(SensorState.linearVelocity);
ConvertPose_Internal(SensorState.pose, Pose, InSettings, InFrame->WorldToMetersScale);
}
break;
}
InFrame->ViewNumber = ViewNumber;
InFrame->Position = Pose.Position;
InFrame->Orientation = Pose.Orientation;
PXR_LOGV(PxrUnreal, "UpdateSensorValue:%u,PredtTime:%f,ViewNumber:%d,Rotation:%s,Position:%s", InFrame->FrameNumber, InFrame->predictedDisplayTimeMs, ViewNumber, PLATFORM_CHAR(*InFrame->Orientation.Rotator().ToString()), PLATFORM_CHAR(*InFrame->Position.ToString()));
#endif
}
void FPICOXRHMD::SetBaseOffsetInMeters(const FVector& BaseOffset)
{
CheckInGameThread();
GameSettings->BaseOffset = BaseOffset;
}
FVector FPICOXRHMD::GetBaseOffsetInMeters() const
{
CheckInGameThread();
return GameSettings->BaseOffset;
}
bool FPICOXRHMD::ConvertPose(const PxrPosef& InPose, FPose& OutPose) const
{
CheckInGameThread();
if (!NextGameFrameToRender_GameThread.IsValid())
{
return false;
}
return ConvertPose_Internal(InPose, OutPose, GameSettings.Get(), NextGameFrameToRender_GameThread->WorldToMetersScale);
}
bool FPICOXRHMD::ConvertPose(const FPose& InPose, PxrPosef& OutPose) const
{
CheckInGameThread();
if (!NextGameFrameToRender_GameThread.IsValid())
{
return false;
}
return ConvertPose_Internal(InPose, OutPose, GameSettings.Get(), NextGameFrameToRender_GameThread->WorldToMetersScale);
}
bool FPICOXRHMD::ConvertPose_Internal(const PxrPosef& InPose, FPose& OutPose, const FGameSettings* Settings, float WorldToMetersScale)
{
return ConvertPose_Private(InPose, OutPose, Settings->BaseOrientation, Settings->BaseOffset, WorldToMetersScale);
}
bool FPICOXRHMD::ConvertPose_Internal(const FPose& InPose, PxrPosef& OutPose, const FGameSettings* Settings, float WorldToMetersScale)
{
return ConvertPose_Private(InPose, OutPose, Settings->BaseOrientation, Settings->BaseOffset, WorldToMetersScale);
}
void FPICOXRHMD::UpdateSplashScreen()
{
if (!GetSplash() || !IsInGameThread())
{
return;
}
if (bSplashIsShown)
{
PXR_LOGD(PxrUnreal, "UpdateSplashScreen Splash->Show()");
}
else
{
PXR_LOGD(PxrUnreal, "UpdateSplashScreen Splash->Hide()");
}
}
void FPICOXRHMD::EnableContentProtect(bool bEnable)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
static jmethodID Method = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "EnableContentProtect", "(Z)V", false);
FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, Method, bEnable);
}
#endif
}
void FPICOXRHMD::ClearTexture_RHIThread(FRHITexture* SrcTexture)
{
if (RHIString == TEXT("Vulkan"))
{
UE_LOG(LogHMD,Log,TEXT("PICO XR Vulkan, Cannot Clear Texture"));
return;
}
check(IsInRenderingThread());
FRHICommandListImmediate& CommandList = FRHICommandListExecutor::GetImmediateCommandList();
const FRHIRenderPassInfo RenderPassInfo(SrcTexture, ERenderTargetActions::Clear_Store);
CommandList.BeginRenderPass(RenderPassInfo, TEXT("ClearTexture"));
CommandList.EndRenderPass();
CommandList.SetViewport(0, 0, 0.0f, SrcTexture->GetSizeX(), SrcTexture->GetSizeY(), 1.0f);
CommandList.Transition(FRHITransitionInfo(SrcTexture, ERHIAccess::Unknown, ERHIAccess::SRVMask));
}
float FPICOXRHMD::UPxr_GetIPD() const
{
PXR_LOGV(PxrUnreal,"const GetIPD %f", IpdValue);
return IpdValue;
}
void FPICOXRHMD::RenderTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture* BackBuffer, FRHITexture* SrcTexture, FVector2D WindowSize) const
{
FHeadMountedDisplayBase::RenderTexture_RenderThread(RHICmdList, BackBuffer, SrcTexture, WindowSize);
}
void FPICOXRHMD::SetupViewFamily(FSceneViewFamily& InViewFamily)
{
CheckInGameThread();
if (GameSettings->Flags.bPauseRendering)
{
InViewFamily.EngineShowFlags.Rendering = 0;
}
}
void FPICOXRHMD::SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView)
{
check(IsInGameThread());
}
void FPICOXRHMD::BeginRenderViewFamily(FSceneViewFamily& InViewFamily)
{
CheckInGameThread();
if (GameSettings.IsValid() && GameSettings->IsStereoEnabled())
{
GameSettings->CurrentShaderPlatform = InViewFamily.Scene->GetShaderPlatform();
GameSettings->Flags.bsRGBEyeBuffer = IsMobilePlatform(GameSettings->CurrentShaderPlatform) && IsMobileColorsRGB();
if (NextGameFrameToRender_GameThread.IsValid())
{
NextGameFrameToRender_GameThread->ShowFlags = InViewFamily.EngineShowFlags;
}
if (SpectatorScreenController != nullptr)
{
SpectatorScreenController->BeginRenderViewFamily();
}
}
OnRenderFrameBegin_GameThread();
}
void FPICOXRHMD::PreRenderView_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView)
{
}
void FPICOXRHMD::PreRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily)
{
CheckInRenderThread();
}
void FPICOXRHMD::PostRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily)
{
const bool bIsSceneCapture = InViewFamily.Views.Num() > 0 && InViewFamily.Views[0]->bIsSceneCapture;
if (!bIsSceneCapture && InViewFamily.Views[0]->StereoPass != EStereoscopicPass::eSSP_FULL)
{
OnRenderFrameEnd_RenderThread(GraphBuilder);
}
}
void FPICOXRHMD::PostRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView)
{
}
#ifdef PICO_CUSTOM_ENGINE
void FPICOXRHMD::PostRenderBasePassMobile_RenderThread(FRHICommandList& RHICmdList, FSceneView& InView)
{
UpdateFoveationOffsets_RenderThread();
}
// void FPICOXRHMD::PostSceneColorRenderingMobile_RenderThread(FRHICommandList& RHICmdList, FSceneView& InView)
// {
// //Todo:Currently this update causes performance issues, to be optimized in the future
// //UpdateFoveationOffsets_RenderThread();
// }
#endif
#ifdef PICO_CUSTOM_ENGINE
bool FPICOXRHMD::LateLatchingEnabled() const
{
if (RHIString == TEXT("Vulkan") && GameSettings)
{
return GameSettings->bLateLatching;
}
return false;
}
void FPICOXRHMD::PreLateLatchingViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily)
{
check(IsInRenderingThread());
FPXRGameFrame* CurrentFrame = GameFrame_RenderThread.Get();
if (CurrentFrame)
{
PXR_LOGV(PxrUnreal, "PreLateLatchingViewFamily_RenderThread:%u", CurrentFrame->FrameNumber);
CurrentFrame->Flags.bLateUpdateOK = false;
}
}
#endif
#ifdef PICO_CUSTOM_ENGINE
bool FPICOXRHMD::NeedReAllocateMotionVectorTexture(const TRefCountPtr<IPooledRenderTarget>& MotionVectorTarget, const TRefCountPtr<IPooledRenderTarget>& MotionVectorDepthTarget)
{
check(IsInRenderingThread())
return bNeedReAllocateMotionVectorTexture_RenderThread;
}
bool FPICOXRHMD::AllocateMotionVectorTexture(uint32 Index, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTextureRHIRef& OutTexture, FIntPoint& OutTextureSize, FTextureRHIRef& OutDepthTexture, FIntPoint& OutDepthTextureSize)
{
PXR_LOGV(PxrUnreal, "AllocateMotionVectorTexture Index=%d, Format=%d, NumMips=%d",Index, Format, NumMips);
check(IsInRenderingThread())
check(Index == 0);
if (PXREyeLayer_RenderThread.IsValid())
{
const FXRSwapChainPtr& SwapChain = PXREyeLayer_RenderThread->GetMotionVectorSwapChain();
if (SwapChain.IsValid())
{
FTextureRHIRef Texture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
FIntPoint TexSize = Texture->GetSizeXY();
const FXRSwapChainPtr& DepthSwapChain = PXREyeLayer_RenderThread->GetMotionVectorDepthSwapChain();
if (DepthSwapChain.IsValid())
{
FTextureRHIRef 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)
{
PXR_LOGI(PxrUnreal, "[Mobile SpaceWarp] Allocating PICO %d x %d motion vector swapchain", TexSize.X, TexSize.Y, Index);
bNeedReAllocateMotionVectorTexture_RenderThread = false;
}
OutTexture = Texture;
OutTextureSize = TexSize;
return true;
}
}
}
return false;
}
#endif
// bool FPICOXRHMD::AllocateRenderTargetTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags Flags, ETextureCreateFlags TargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples)
// {
// PXR_LOGI(PxrUnreal, "AllocateRenderTargetTexture Index=%d, SizeX=%d, SizeY=%d, Format=%d, NumMips=%d, Flags=%d, TargetableTextureFlags=%d, NumSamples=%d",
// Index, SizeX, SizeY, Format, NumMips, Flags, TargetableTextureFlags, NumSamples);
//
// check(IsInRenderingThread());
//
// check(Index == 0);
//
// if (PXRLayerMap[0].IsValid() && PXREyeLayer_RenderThread.IsValid())
// {
// const FXRSwapChainPtr& SwapChain = PXREyeLayer_RenderThread->GetSwapChain();
// if (SwapChain.IsValid())
// {
// OutTargetableTexture = OutShaderResourceTexture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
// bNeedReAllocateViewportRenderTarget = false;
// return true;
// }
// }
//
// return false;
// }
bool FPICOXRHMD::AllocateRenderTargetTextures(uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumLayers, ETextureCreateFlags Flags, ETextureCreateFlags TargetableTextureFlags, TArray<FTextureRHIRef>& OutTargetableTextures, TArray<FTextureRHIRef>& OutShaderResourceTextures, uint32 NumSamples)
{
PXR_LOGI(PxrUnreal, "AllocateRenderTargetTexture SizeX=%d, SizeY=%d, Format=%d, NumLayers=%d, Flags=%d, TargetableTextureFlags=%d, NumSamples=%d",
SizeX, SizeY, Format, NumLayers, Flags, TargetableTextureFlags, NumSamples);
check(IsInRenderingThread());
if (PXRLayerMap[0].IsValid() && PXREyeLayer_RenderThread.IsValid())
{
const FXRSwapChainPtr& SwapChain = PXREyeLayer_RenderThread->GetSwapChain();
if (SwapChain.IsValid())
{
OutTargetableTextures.Add(SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D());
OutShaderResourceTextures = OutTargetableTextures;
bNeedReAllocateViewportRenderTarget = false;
return true;
}
}
return false;
}
bool FPICOXRHMD::NeedReAllocateShadingRateTexture(const TRefCountPtr<IPooledRenderTarget>& FoveationTarget)
{
CheckInRenderThread();
return GameSettings_RenderThread->IsStereoEnabled() && bNeedReAllocateFoveationTexture_RenderThread;
}
bool FPICOXRHMD::AllocateShadingRateTexture(uint32 Index, uint32 RenderSizeX, uint32 RenderSizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTextureRHIRef& OutTexture, FIntPoint& OutTextureSize)
{
check(Index == 0 && (IsInRenderingThread() || IsInRHIThread()));
#if PLATFORM_ANDROID
if (RHIString == TEXT("OpenGL") || !FPICOXRHMDModule::GetPluginWrapper().GetFeatureSupported(PXR_FEATURE_FOVEATION))
{
PXR_LOGI(PxrUnreal, "AllocateFoveationTexture OpenGL Graphics & Feature Foveation is Not Supportted");
return false;
}
#endif
if (PXREyeLayer_RenderThread && PXREyeLayer_RenderThread.IsValid())
{
const FXRSwapChainPtr& SwapChain = PXREyeLayer_RenderThread->GetFoveationSwapChain();
if (SwapChain.IsValid())
{
PXR_LOGI(PxrUnreal, "AllocateFoveationTexture SwapChain.IsValid");
FTextureRHIRef Texture=nullptr;
Texture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D();
FIntPoint TexSize = Texture->GetSizeXY();
if (Texture->IsValid() && TexSize.X > 0 && TexSize.Y > 0 )
{
if (bNeedReAllocateFoveationTexture_RenderThread)
{
bNeedReAllocateFoveationTexture_RenderThread = false;
}
if (RenderSizeX % TexSize.X != 0 || RenderSizeY % TexSize.Y != 0)
{
PXR_LOGI(PxrUnreal, "%d x %d variable resolution swapchain is not a divider of %d x %d color swapchain, potential edge problems", TexSize.X, TexSize.Y, RenderSizeX, RenderSizeY);
}
else
{
PXR_LOGI(PxrUnreal, "%d x %d variable resolution swapchain is a divider of %d x %d color swapchain, no edge problems", TexSize.X, TexSize.Y, RenderSizeX, RenderSizeY);
}
OutTexture = Texture;
OutTextureSize = TexSize;
return true;
}
}
}
return false;
}
void FPICOXRHMD::CalculateRenderTargetSize(const class FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY)
{
if (!GameSettings->IsStereoEnabled())
{
return;
}
InOutSizeX = GameSettings->RenderTargetSize.X;
InOutSizeY = GameSettings->RenderTargetSize.Y;
check(InOutSizeX != 0 && InOutSizeY != 0);
}
bool FPICOXRHMD::NeedReAllocateViewportRenderTarget(const FViewport& Viewport)
{
CheckInGameThread();
return GameSettings->IsStereoEnabled() && bNeedReAllocateViewportRenderTarget;
}
FXRRenderBridge* FPICOXRHMD::GetActiveRenderBridge_GameThread(bool bUseSeparateRenderTarget)
{
return RenderBridge;
}
uint32 FPICOXRHMD::CreateLayer(const FLayerDesc& InLayerDesc)
{
check(IsInGameThread());
uint32 LayerId = NextLayerId++;
PXRLayerMap.Add(LayerId, MakeShareable(new FPICOXRStereoLayer(this, LayerId, InLayerDesc)));
PXR_LOGD(PxrUnreal, "Layer Create LayerId=%d", LayerId);
if (LayerId!=0&&!InLayerDesc.Texture.IsValid())
{
MakeAllStereolayerComponentsUpdate();
}
return LayerId;
}
void FPICOXRHMD::DestroyLayer(uint32 LayerId)
{
check(IsInGameThread());
PXR_LOGD(PxrUnreal, "DestroyLayer LayerId=%d", LayerId);
FPICOLayerPtr* LayerFound = PXRLayerMap.Find(LayerId);
if (LayerFound)
{
(*LayerFound)->DestroyUnderlayMesh();
}
PXRLayerMap.Remove(LayerId);
}
void FPICOXRHMD::SetLayerDesc(uint32 LayerId, const FLayerDesc& InLayerDesc)
{
check(IsInGameThread());
FPICOLayerPtr* LayerFound = PXRLayerMap.Find(LayerId);
if (LayerFound)
{
FPICOXRStereoLayer* Layer = new FPICOXRStereoLayer(**LayerFound);
Layer->SetPXRLayerDesc(InLayerDesc);
*LayerFound = MakeShareable(Layer);
}
}
bool FPICOXRHMD::GetLayerDesc(uint32 LayerId, IStereoLayers::FLayerDesc& OutLayerDesc)
{
check(IsInGameThread());
FPICOLayerPtr* LayerFound = PXRLayerMap.Find(LayerId);
if (LayerFound)
{
OutLayerDesc = (*LayerFound)->GetPXRLayerDesc();
return true;
}
return false;
}
void FPICOXRHMD::MarkTextureForUpdate(uint32 LayerId)
{
check(IsInGameThread());
FPICOLayerPtr* LayerFound = PXRLayerMap.Find(LayerId);
if (LayerFound)
{
(*LayerFound)->MarkTextureForUpdate();
}
MakeAllStereoLayerComponentsDirty();
}
IStereoLayers::FLayerDesc FPICOXRHMD::GetDebugCanvasLayerDesc(FTextureRHIRef Texture)
{
IStereoLayers::FLayerDesc StereoLayerDesc;
StereoLayerDesc.Priority = INT_MAX;
StereoLayerDesc.Transform = FTransform(FVector(180.f, 0, 0));
float scaleZ = -1.0f;
if (RHIString == TEXT("Vulkan"))
scaleZ = 1.0f;
StereoLayerDesc.Transform.SetScale3D(FVector(1,1, scaleZ));
StereoLayerDesc.QuadSize = FVector2D(180.f, 180.f);
StereoLayerDesc.LayerSize = Texture->GetTexture2D()->GetSizeXY();
StereoLayerDesc.PositionType = IStereoLayers::ELayerType::FaceLocked;
StereoLayerDesc.SetShape<FQuadLayer>();
StereoLayerDesc.Texture = Texture;
StereoLayerDesc.Flags = IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_CONTINUOUS_UPDATE;
StereoLayerDesc.Flags |= IStereoLayers::ELayerFlags::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO;
return StereoLayerDesc;
}
void FPICOXRHMD::GetAllocatedTexture(uint32 LayerId, FTextureRHIRef& Texture, FTextureRHIRef& LeftTexture)
{
Texture = LeftTexture = nullptr;
}
void FPICOXRHMD::OnBeginPlay(FWorldContext& InWorldContext)
{
GEngine->bUseFixedFrameRate = false;
bNeedDrawBlackEye = false;
IConsoleVariable* CVSynsVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.VSync"));
CVSynsVar->Set(0.0f);
IConsoleVariable* CFCFVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.FinishCurrentFrame"));
CFCFVar->Set(0.0f);
PlayerController = InWorldContext.World()->GetFirstPlayerController();
EventManager->RawLongHomePressedDelegate.AddRaw(this,&FPICOXRHMD::OnHomeKeyRecentered);
#ifdef PICO_CUSTOM_ENGINE
// Sync GameUserSettings with PXR Settings
UGameUserSettings* GameUserSettings = GEngine->GetGameUserSettings();
if (GameUserSettings)
{
GameUserSettings->SetDynamicResolutionEnabled(GameSettings->IsAdaptiveResolutionEnabled());
GameUserSettings->ApplyNonResolutionSettings();
}
#endif
//Initialize the power of the headset, and update by PollEvent
UpdateHMDBatteryLevelFromJava(CurrentHMDBatteryLevel);
}
void FPICOXRHMD::OnEndPlay(FWorldContext& InWorldContext)
{
}
void FPICOXRHMD::GetMotionControllerData(UObject* WorldContext, const EControllerHand Hand, FXRMotionControllerData& MotionControllerData)
{
MotionControllerData.DeviceName = GetSystemName();
MotionControllerData.ApplicationInstanceID = FApp::GetInstanceId();
MotionControllerData.DeviceVisualType = EXRVisualType::Controller;
MotionControllerData.TrackingStatus = ETrackingStatus::NotTracked;
MotionControllerData.HandIndex = Hand;
FTransform TrackingToWorld = GetTrackingToWorldTransform();
FName MotionControllerName("PICOXRInput");
TArray<IMotionController*> MotionControllers = IModularFeatures::Get().GetModularFeatureImplementations<IMotionController>(IMotionController::GetModularFeatureName());
FXRMotionControllerBase* MotionController = nullptr;
for (auto Itr : MotionControllers)
{
if (Itr->GetMotionControllerDeviceTypeName() == MotionControllerName)
{
MotionController = static_cast<FXRMotionControllerBase*>(Itr);
break;
}
}
if (MotionController)
{
FName MotionSource;
GetSourceNameForHandEnum(Hand, MotionSource);
FRotator GripTrackingRotation;
FVector GripTrackingPosition;
MotionControllerData.bValid = MotionController->GetControllerOrientationAndPosition(0 , MotionSource, GripTrackingRotation, GripTrackingPosition, GetWorldToMetersScale());
if (MotionControllerData.bValid)
{
MotionControllerData.TrackingStatus=ETrackingStatus::Tracked;
const FTransform GripTrackingTransform(GripTrackingRotation.Quaternion(), GripTrackingPosition);
MotionControllerData.GripPosition = TrackingToWorld.TransformPosition(GripTrackingTransform.GetLocation());
MotionControllerData.GripRotation = TrackingToWorld.TransformRotation(FQuat(GripTrackingTransform.GetRotation()));
MotionControllerData.AimPosition = MotionControllerData.GripPosition;
MotionControllerData.AimRotation = MotionControllerData.GripRotation;
}
}
}
int32 FPICOXRHMD::GetXRSystemFlags() const
{
return EXRSystemFlags::IsHeadMounted;
}
EHMDWornState::Type FPICOXRHMD::GetHMDWornState()
{
if (PICOXRSetting->bEnablePSensor)
{
int32 state = -1;
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
static jmethodID Method = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "GetPsensorState", "()I", false);
state = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, Method);
}
#endif
if (state == 0)
{
return EHMDWornState::Worn;
}
else
{
return EHMDWornState::NotWorn;
}
}
return EHMDWornState::Unknown;
}
float FPICOXRHMD::GetPixelDenity() const
{
if (IsInGameThread())
{
return GameSettings.IsValid() ? GameSettings->PixelDensity : 1.0f;
}
else
{
return GameSettings_RenderThread.IsValid() ? GameSettings_RenderThread->PixelDensity : 1.0f;
}
}
void FPICOXRHMD::SetPixelDensity(const float NewPixelDensity)
{
PXR_LOGV(PxrUnreal, "SetPixelDensity = %f", NewPixelDensity);
CheckInGameThread();
GameSettings->SetPixelDensity(NewPixelDensity);
}
void FPICOXRHMD::SetRefreshRate()
{
#if PLATFORM_ANDROID
switch (PICOXRSetting->refreshRate)
{
case ERefreshRate::Default:
{
FPICOXRHMDModule::GetPluginWrapper().SetDisplayRefreshRate(0.0f);
break;
}
case ERefreshRate::RefreshRate72:
{
FPICOXRHMDModule::GetPluginWrapper().SetDisplayRefreshRate(72.0f);
break;
}
case ERefreshRate::RefreshRate90:
{
FPICOXRHMDModule::GetPluginWrapper().SetDisplayRefreshRate(90.0f);
break;
}
case ERefreshRate::RefreshRate120:
{
FPICOXRHMDModule::GetPluginWrapper().SetDisplayRefreshRate(120.0f);
break;
}
default:
break;
}
#endif
}
bool FPICOXRHMD::UpdateHMDBatteryLevelFromJava(int32& BatteryLevel)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
static jmethodID Method = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "GetHmdBatteryLevel", "()I", false);
BatteryLevel=FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, Method);
return true;
}
#endif
return false;
}
bool FPICOXRHMD::UpdateHMDBatteryLevelFromPollEvent(int32& BatteryLevel)
{
if (CurrentHMDBatteryLevel)
{
BatteryLevel=CurrentHMDBatteryLevel;
return true;
}
return false;
}
bool FPICOXRHMD::SetCurrentCoordinateType(EPICOXRCoordinateType InCoordinateType)
{
GameSettings->CoordinateType=InCoordinateType;
return true;
}
#ifdef PICO_CUSTOM_ENGINE
bool FPICOXRHMD::IsSupportsSpaceWarp() const
{
#if PLATFORM_ANDROID
static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView"));
static const auto CVarSupportMobileSpaceWarp = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.SupportMobileSpaceWarp"));
bool bIsMultiViewEnabled = (CVarMobileMultiView && CVarMobileMultiView->GetValueOnAnyThread() != 0);
//TODO:Optimize for dynamic fetching
bool bIsVulkan = RHIString == TEXT("Vulkan");
bool spaceWarpSupported = bIsVulkan &&bIsUsingMobileMultiView && CVarSupportMobileSpaceWarp && (CVarSupportMobileSpaceWarp->GetValueOnAnyThread() != 0);
return spaceWarpSupported;
#else
return false;
#endif
}
#endif
void FPICOXRHMD::SetColorScaleAndOffset(FLinearColor ColorScale, FLinearColor ColorOffset, bool bApplyToAllLayers)
{
PXR_LOGV(PxrUnreal,"PICOXRSetColorScaleAndOffset Scale(RGBA)= %f %f %f %f. Offset = %f %f %f %f,AllLayers = %d",ColorScale.R,ColorScale.G,ColorScale.B,ColorScale.A,ColorOffset.R,ColorOffset.G,ColorOffset.B,ColorOffset.A,bApplyToAllLayers);
CheckInGameThread();
GameSettings->bApplyColorScaleAndOffsetToAllLayers = bApplyToAllLayers;
GameSettings->ColorScale = LinearColorToPxrVector4f(ColorScale);
GameSettings->ColorOffset = LinearColorToPxrVector4f(ColorOffset);
}
uint32 FPICOXRHMD::CreateMRCStereoLayer(FTextureRHIRef BackgroundRTTexture, FTextureRHIRef ForegroundRTTexture)
{
IStereoLayers::FLayerDesc StereoLayerDesc;
StereoLayerDesc.PositionType = IStereoLayers::ELayerType::FaceLocked;
StereoLayerDesc.Texture = ForegroundRTTexture;
StereoLayerDesc.LeftTexture = BackgroundRTTexture;
StereoLayerDesc.Flags = IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_CONTINUOUS_UPDATE | IStereoLayers::ELayerFlags::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO | IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL;
check(IsInGameThread());
if (CurrentMRCLayer)
{
return CurrentMRCLayer->GetID();
}
const uint32 LayerId = NextLayerId++;
PXR_LOGD(PxrUnreal, "MRC Layer Create LayerId=%d", LayerId);
CurrentMRCLayer= MakeShareable(new FPICOXRStereoLayer(this, LayerId, StereoLayerDesc));
PXRLayerMap.Add(LayerId, CurrentMRCLayer);
PXRLayerMap[LayerId]->bMRCLayer = true;
return LayerId;
}
void FPICOXRHMD::DestroyMRCLayer()
{
if (CurrentMRCLayer)
{
DestroyLayer(CurrentMRCLayer->GetID());
CurrentMRCLayer.Reset();
MRCCamera = nullptr;
}
}
FString FPICOXRHMD::GetRHIString()
{
return RHIString;
}
void FPICOXRHMD::OnPreLoadMap(const FString& MapName)
{
PXR_LOGD(PxrUnreal, "OnPreLoadMap:%s", PLATFORM_CHAR(*MapName));
bNeedDrawBlackEye = true;
if (PICOSplash)
{
PICOSplash->OnPreLoadMap(MapName);
}
}
DECLARE_STATS_GROUP(TEXT("PICOTiming"), STATGROUP_PICOTiming, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("WaitFrame"), STAT_WaitFrame, STATGROUP_PICOTiming);
void FPICOXRHMD::WaitFrame()
{
#if PLATFORM_ANDROID
SCOPE_CYCLE_COUNTER(STAT_WaitFrame);
check(IsInGameThread());
if (GameFrame_GameThread.IsValid())
{
PXR_LOGV(PxrUnreal, "WaitFrame %u", GameFrame_GameThread->FrameNumber);
if (!PICOSplash->IsShown() && WaitedFrameNumber < GameFrame_GameThread->FrameNumber)
{
if (bWaitFrameVersion)
{
FPICOXRHMDModule::GetPluginWrapper().WaitFrame();
FPICOXRHMDModule::GetPluginWrapper().GetPredictedDisplayTime(&CurrentFramePredictedTime);
GameFrame_GameThread->Flags.bHasWaited = true;
GameFrame_GameThread->predictedDisplayTimeMs = CurrentFramePredictedTime;
PXR_LOGV(PxrUnreal, "Pxr_GetPredictedDisplayTime after wait frame %u,Time:%f", GameFrame_GameThread->FrameNumber, CurrentFramePredictedTime);
}
else
{
GameFrame_GameThread->Flags.bHasWaited = true;
}
WaitedFrameNumber = GameFrame_GameThread->FrameNumber;
PXR_LOGV(PxrUnreal, "Wait frame return %u", GameFrame_GameThread->FrameNumber);
}
else
{
PXR_LOGV(PxrUnreal, "WaitFrame not wait! %u,bSplashIsShowing:%d,WaitedFrameNumber:%u", GameFrame_GameThread->FrameNumber, PICOSplash->IsShown(), WaitedFrameNumber);
}
}
#endif
}
void FPICOXRHMD::LateUpdatePose()
{
check(IsInRenderingThread());
FPXRGameFrame* CurrentFrame = GameFrame_RenderThread.Get();
if (CurrentFrame)
{
if (!CurrentFrame->Flags.bLateUpdateOK)
{
UpdateSensorValue(GameSettings_RenderThread.Get(), CurrentFrame);
CurrentFrame->Flags.bLateUpdateOK = true;
int32 SubmitViewNumber = CurrentFrame->ViewNumber;
ExecuteOnRHIThread_DoNotWait([this, SubmitViewNumber]()
{
FPXRGameFrame* CurrentFrame_RHIThread = GameFrame_RHIThread.Get();
if (CurrentFrame_RHIThread)
{
CurrentFrame_RHIThread->ViewNumber = SubmitViewNumber;
}
});
}
}
}
void FPICOXRHMD::OnGameFrameBegin_GameThread()
{
CheckInGameThread();
check(GameSettings.IsValid());
#if PLATFORM_ANDROID
if (!GameFrame_GameThread.IsValid() && FPICOXRHMDModule::GetPluginWrapper().IsRunning())
{
static const auto WaitFrameAtGameFrameTailCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("PICO.WaitFrameAtGameFrameTail"));
GameSettings->bWaitFrameAtGameFrameTail = WaitFrameAtGameFrameTailCVar && WaitFrameAtGameFrameTailCVar->GetValueOnAnyThread() != 0;
PICOSplash->SwitchActiveSplash_GameThread();
if (GameSettings->Flags.bHMDEnabled)
{
GameFrame_GameThread = MakeNewGameFrame();
NextGameFrameToRender_GameThread = GameFrame_GameThread;
PXR_LOGV(PxrUnreal, "StartGameFrame %u", GameFrame_GameThread->FrameNumber);
if (!PICOSplash->IsShown())
{
if (!GameSettings->bWaitFrameAtGameFrameTail)
{
PXR_LOGV(PxrUnreal, "Wait frame at GameFrame head.");
WaitFrame();
}
UpdateSensorValue(GameSettings.Get(), NextGameFrameToRender_GameThread.Get());
}
}
UpdateStereoRenderingParams();
}
#endif
}
void FPICOXRHMD::OnGameFrameEnd_GameThread()
{
CheckInGameThread();
check(GameSettings.IsValid());
if (GameSettings->bWaitFrameAtGameFrameTail)
{
PXR_LOGV(PxrUnreal, "Wait frame at GameFrame tail.");
WaitFrame();
}
if (GameFrame_GameThread.IsValid())
{
PXR_LOGV(PxrUnreal, "OnGameFrameEnd %u", GameFrame_GameThread->FrameNumber);
}
GameFrame_GameThread.Reset();
}
void FPICOXRHMD::OnRenderFrameBegin_GameThread()
{
check(IsInGameThread());
if (NextGameFrameToRender_GameThread.IsValid() && NextGameFrameToRender_GameThread->Flags.bHasWaited && NextGameFrameToRender_GameThread!=LastGameFrameToRender_GameThread)
{
LastGameFrameToRender_GameThread = NextGameFrameToRender_GameThread;
NextGameFrameToRender_GameThread->Flags.bSplashIsShown = PICOSplash->IsShown();
if (NextGameFrameToRender_GameThread->ShowFlags.Rendering && !NextGameFrameToRender_GameThread->Flags.bSplashIsShown)
{
NextGameFrameNumber++;
}
FSettingsPtr PXRSettings = GameSettings->Clone();
FPXRGameFramePtr PXRFrame = NextGameFrameToRender_GameThread->CloneMyself();
PXR_LOGV(PxrUnreal, "OnRenderFrameBegin_GameThread %u has been eaten by render-thread!", NextGameFrameToRender_GameThread->FrameNumber);
TArray<FPICOLayerPtr> PXRLayers;
PXRLayers.Empty(PXRLayerMap.Num());
for (auto Pair : PXRLayerMap)
{
PXRLayers.Emplace(Pair.Value->CloneMyself());
if (Pair.Value->GetPXRLayerDesc().Flags & IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE && Pair.Value->GetPXRLayerDesc().Texture.IsValid())
{
Pair.Value->MarkTextureForUpdate(true);
}
else
{
Pair.Value->MarkTextureForUpdate(false);
}
}
PXRLayers.Sort(FPICOLayerPtr_SortById());
ExecuteOnRenderThread_DoNotWait([this, PXRSettings, PXRFrame, PXRLayers](FRHICommandListImmediate& RHICmdList)
{
if (PXRFrame.IsValid())
{
GameSettings_RenderThread = PXRSettings;
GameFrame_RenderThread = PXRFrame;
int32 PXRLayerIndex_Current = 0;
int32 PXRLastLayerIndex_RenderThread = 0;
TArray<FPICOLayerPtr> ValidXLayers;
while (PXRLayerIndex_Current < PXRLayers.Num() && PXRLastLayerIndex_RenderThread < PXRLayers_RenderThread.Num())
{
uint32 LayerIdX = PXRLayers[PXRLayerIndex_Current]->GetID();
uint32 LayerIdY = PXRLayers_RenderThread[PXRLastLayerIndex_RenderThread]->GetID();
if (LayerIdX < LayerIdY)
{
if (PXRLayers[PXRLayerIndex_Current]->InitPXRLayer_RenderThread(GameSettings_RenderThread.Get(), RenderBridge, &DelayDeletion, RHICmdList))
{
ValidXLayers.Add(PXRLayers[PXRLayerIndex_Current]);
}
PXRLayerIndex_Current++;
}
else if (LayerIdX > LayerIdY)
{
DelayDeletion.AddLayerToDeferredDeletionQueue(PXRLayers_RenderThread[PXRLastLayerIndex_RenderThread++]);
}
else
{
if (PXRLayers[PXRLayerIndex_Current]->InitPXRLayer_RenderThread(GameSettings_RenderThread.Get(), RenderBridge, &DelayDeletion, RHICmdList, PXRLayers_RenderThread[PXRLastLayerIndex_RenderThread].Get()))
{
PXRLastLayerIndex_RenderThread++;
ValidXLayers.Add(PXRLayers[PXRLayerIndex_Current]);
}
PXRLayerIndex_Current++;
}
}
while (PXRLayerIndex_Current < PXRLayers.Num())
{
if (PXRLayers[PXRLayerIndex_Current]->InitPXRLayer_RenderThread(GameSettings_RenderThread.Get(), RenderBridge, &DelayDeletion, RHICmdList))
{
ValidXLayers.Add(PXRLayers[PXRLayerIndex_Current]);
}
PXRLayerIndex_Current++;
}
while (PXRLastLayerIndex_RenderThread < PXRLayers_RenderThread.Num())
{
DelayDeletion.AddLayerToDeferredDeletionQueue(PXRLayers_RenderThread[PXRLastLayerIndex_RenderThread++]);
}
PXRLayers_RenderThread = ValidXLayers;
DelayDeletion.HandleLayerDeferredDeletionQueue_RenderThread();
}
});
}
}
void FPICOXRHMD::OnRenderFrameEnd_RenderThread(FRDGBuilder& RDGBuilder)
{
check(IsInRenderingThread());
// bIsRendering_RenderThread is to keep Frame_RenderThread alive if we haven't started to use it to render yet!
if (!bIsRendering_RenderThread)
{
return;
}
AddPass(RDGBuilder, RDG_EVENT_NAME("RenderFrameEnd"), [this](FRHICommandListImmediate& RHICmdList)
{
if (GameFrame_RenderThread.IsValid() && !PICOSplash->IsShown())
{
if (GameFrame_RenderThread->ShowFlags.Rendering)
{
for (int32 i = 0; i < PXRLayers_RenderThread.Num(); i++)
{
PXRLayers_RenderThread[i]->PXRLayersCopy_RenderThread(RenderBridge, RHICmdList);
}
}
}
GameFrame_RenderThread.Reset();
});
bIsRendering_RenderThread = false;
}
void FPICOXRHMD::OnRHIFrameBegin_RenderThread()
{
check(IsInRenderingThread());
if (GameFrame_RenderThread.IsValid())
{
FSettingsPtr PXRSettings = GameSettings_RenderThread->Clone();
FPXRGameFramePtr PXRFrame = GameFrame_RenderThread->CloneMyself();
TArray<FPICOLayerPtr> PXRLayers = PXRLayers_RenderThread;
for (int32 XLayerIndex = 0; XLayerIndex < PXRLayers.Num(); XLayerIndex++)
{
PXRLayers[XLayerIndex] = PXRLayers[XLayerIndex]->CloneMyself();
}
ExecuteOnRHIThread_DoNotWait([this, PXRSettings, PXRFrame, PXRLayers]()
{
#if PLATFORM_ANDROID
if (PXRFrame.IsValid())
{
GameSettings_RHIThread = PXRSettings;
GameFrame_RHIThread = PXRFrame;
PXRLayers_RHIThread = PXRLayers;
PXR_LOGV(PxrUnreal, "BeginFrame %u", GameFrame_RHIThread->FrameNumber);
if (GameFrame_RHIThread->ShowFlags.Rendering && !GameFrame_RHIThread->Flags.bSplashIsShown)
{
if (FPICOXRHMDModule::GetPluginWrapper().IsRunning())
{
FPICOXRHMDModule::GetPluginWrapper().BeginFrame();
if (!bWaitFrameVersion)
{
FPICOXRHMDModule::GetPluginWrapper().GetPredictedDisplayTime(&CurrentFramePredictedTime);
PXR_LOGV(PxrUnreal, "Pxr_GetPredictedDisplayTime after begin frame:%f", CurrentFramePredictedTime);
}
for (int32 LayerIndex = 0; LayerIndex < PXRLayers_RHIThread.Num(); LayerIndex++)
{
PXRLayers_RHIThread[LayerIndex]->IncrementSwapChainIndex_RHIThread(RenderBridge);
}
}
else
{
PXR_LOGE(PxrUnreal, "Pxr Is Not Running!!!");
}
}
}
#endif
});
bIsRendering_RenderThread = true;
}
}
void FPICOXRHMD::OnRHIFrameEnd_RHIThread()
{
check(IsInRHIThread() || IsInRenderingThread());
if (GameFrame_RHIThread.IsValid())
{
#if PLATFORM_ANDROID
PXR_LOGV(PxrUnreal, "EndFrame %u,SubmitViewNum:%d,Rotation:%s,Position:%s", GameFrame_RHIThread->FrameNumber, GameFrame_RHIThread->ViewNumber,
PLATFORM_CHAR(*(GameFrame_RHIThread->Orientation.Rotator().ToString())), PLATFORM_CHAR(*(GameFrame_RHIThread->Position.ToString())));
if (GameFrame_RHIThread->ShowFlags.Rendering && !GameFrame_RHIThread->Flags.bSplashIsShown)
{
TArray<FPICOLayerPtr> Layers = PXRLayers_RHIThread;
Layers.Sort(FLayerPtr_CompareByAll());
if (FPICOXRHMDModule::GetPluginWrapper().IsRunning())
{
for (int32 LayerIndex = 0; LayerIndex < Layers.Num(); LayerIndex++)
{
if (Layers[LayerIndex]->IsVisible())
{
Layers[LayerIndex]->SubmitLayer_RHIThread(GameSettings_RHIThread.Get(), GameFrame_RHIThread.Get());
}
}
FPICOXRHMDModule::GetPluginWrapper().EndFrame();
}
else
{
PXR_LOGE(PxrUnreal, "Pxr Is Not Running!!!");
}
}
#endif
}
GameFrame_RHIThread.Reset();
}
FSettingsPtr FPICOXRHMD::CreateNewSettings() const
{
FSettingsPtr Result(MakeShareable(new FGameSettings()));
return Result;
}