October3d55/M/PICOOpenXR/Source/PICOOpenXRHMD/Private/PICO_HMD.cpp

1618 lines
55 KiB
C++
Raw Normal View History

2025-03-10 09:43:27 +08:00
// Copyright 2023 PICO Inc. All Rights Reserved.
#include "PICO_HMD.h"
#include "PICOOpenXRRuntimeSettings.h"
#include "OpenXRCore.h"
#include "IXRTrackingSystem.h"
#include "OpenXRCore.h"
#include "IOpenXRHMDModule.h"
#include "OpenXRHMD_Swapchain.h"
#include "OpenXRHMD_RenderBridge.h"
#include "PixelShaderUtils.h"
#include "Shader.h"
#include "ClearQuad.h"
#include "ScreenRendering.h"
#include "PICO_Shaders.h"
#include "HDRHelper.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "TextureResource.h"
#include "GameFramework/Actor.h"
#include "PICO_MRCCamera.h"
#include "Runtime/Launch/Resources/Version.h"
2025-07-21 10:22:56 +08:00
#include "PICO_DynamicResolutionState.h"
2025-03-10 09:43:27 +08:00
#if PLATFORM_ANDROID
#include <dlfcn.h>
#endif //PLATFORM_ANDROID
static TAutoConsoleVariable<int32> CVarPICOEnableSuperResolution(
TEXT("r.Mobile.PICO.EnableSuperResolution"),
0,
TEXT("0: Disable SuperResolution (Default)\n")
TEXT("1: Enable SuperResolution on supported platforms\n"),
2025-07-21 10:22:56 +08:00
ECVF_Default);
2025-03-10 09:43:27 +08:00
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"),
2025-07-21 10:22:56 +08:00
ECVF_Default);
2025-03-10 09:43:27 +08:00
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"),
2025-07-21 10:22:56 +08:00
ECVF_Default);
static TAutoConsoleVariable<float> CVarPICODynamicResolutionPixelDensity(
TEXT("r.PICO.DynamicResolution.PixelDensity"),
0,
TEXT("0 Static Pixel Density corresponding to Pixel Density 1.0 (default)\n")
TEXT(">0 Manual Pixel Density Override\n"),
ECVF_Default);
2025-03-10 09:43:27 +08:00
FHMDPICO::FHMDPICO()
: LoaderHandle(nullptr)
, bPICORuntime(false)
, Instance(XR_NULL_HANDLE)
, System(XR_NULL_SYSTEM_ID)
, Session(XR_NULL_HANDLE)
, bSupportLocalFloorLevelEXT(false)
, bSupportDisplayRefreshRate(false)
, CurrentDisplayRefreshRate(0)
, bContentProtectEnabled(false)
, CurrentDisplayTime(0)
, IsSupportsUserPresence(false)
, WornState(EHMDWornState::Type::Unknown)
, bSupportMRCExtension(false)
, bIsMRCRunning(false)
, MRCDebugMode(FMRCDebugModePICO())
, bSupportedBDCompositionLayerSettingsExt(false)
2025-07-21 10:22:56 +08:00
, ProjectionLayerSettings({ XR_TYPE_LAYER_SETTINGS_PICO })
2025-03-10 09:43:27 +08:00
{
SupportedDisplayRefreshRates.Empty();
2025-07-21 10:22:56 +08:00
BlendState.srcFactorColor = XrBlendFactorFB::XR_BLEND_FACTOR_DST_ALPHA_FB;
BlendState.dstFactorColor = XrBlendFactorFB::XR_BLEND_FACTOR_ONE_MINUS_DST_ALPHA_FB;
BlendState.srcFactorAlpha = XrBlendFactorFB::XR_BLEND_FACTOR_ZERO_FB;
BlendState.dstFactorAlpha = XrBlendFactorFB::XR_BLEND_FACTOR_ONE_FB;
2025-03-10 09:43:27 +08:00
}
void FHMDPICO::Register()
{
RegisterOpenXRExtensionModularFeature();
2025-07-21 10:22:56 +08:00
OnWorldTickStartDelegateHandle = FWorldDelegates::OnWorldTickStart.AddRaw(this, &FHMDPICO::OnWorldTickStart);
2025-03-10 09:43:27 +08:00
}
void FHMDPICO::Unregister()
{
UnregisterOpenXRExtensionModularFeature();
if (LoaderHandle)
{
FPlatformProcess::FreeDllHandle(LoaderHandle);
LoaderHandle = nullptr;
}
2025-07-21 10:22:56 +08:00
if (OnWorldTickStartDelegateHandle.IsValid())
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
FWorldDelegates::OnWorldTickStart.Remove(OnWorldTickStartDelegateHandle);
OnWorldTickStartDelegateHandle.Reset();
2025-03-10 09:43:27 +08:00
}
}
bool FHMDPICO::GetCustomLoader(PFN_xrGetInstanceProcAddr* OutGetProcAddr)
{
#if PLATFORM_ANDROID
// clear errors
dlerror();
LoaderHandle = FPlatformProcess::GetDllHandle(TEXT("libopenxr_loader_pico.so"));
if (LoaderHandle == nullptr)
{
UE_LOG(LogPICOOpenXRHMD, Error, TEXT("Unable to load libopenxr_loader_pico.so, error %s"), ANSI_TO_TCHAR(dlerror()));
return false;
}
// clear errors
dlerror();
PFN_xrGetInstanceProcAddr xrGetInstanceProcAddrPtr = (PFN_xrGetInstanceProcAddr)FPlatformProcess::GetDllExport(LoaderHandle, TEXT("xrGetInstanceProcAddr"));
if (xrGetInstanceProcAddrPtr == nullptr)
{
UE_LOG(LogPICOOpenXRHMD, Error, TEXT("Unable to load OpenXR xrGetInstanceProcAddr, error %s"), ANSI_TO_TCHAR(dlerror()));
return false;
}
*OutGetProcAddr = xrGetInstanceProcAddrPtr;
extern struct android_app* GNativeAndroidApp;
PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
xrGetInstanceProcAddrPtr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
if (xrInitializeLoaderKHR == nullptr)
{
UE_LOG(LogPICOOpenXRHMD, Error, TEXT("Unable to load OpenXR xrInitializeLoaderKHR"));
return false;
}
XrLoaderInitInfoAndroidKHR LoaderInitializeInfoAndroid;
LoaderInitializeInfoAndroid.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
LoaderInitializeInfoAndroid.next = NULL;
LoaderInitializeInfoAndroid.applicationVM = GNativeAndroidApp->activity->vm;
LoaderInitializeInfoAndroid.applicationContext = GNativeAndroidApp->activity->clazz;
XR_ENSURE(xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&LoaderInitializeInfoAndroid));
//Used to determine whether the pico runtime loads successfully
{
PFN_xrEnumerateInstanceExtensionProperties xrEnumerateInstanceExtensionPropertiesPtr;
xrGetInstanceProcAddrPtr(XR_NULL_HANDLE, "xrEnumerateInstanceExtensionProperties", (PFN_xrVoidFunction*)&xrEnumerateInstanceExtensionPropertiesPtr);
if (xrEnumerateInstanceExtensionPropertiesPtr == nullptr)
{
UE_LOG(LogPICOOpenXRHMD, Error, TEXT("Unable to load OpenXR xrEnumerateInstanceExtensionProperties!"));
return false;
}
uint32_t ExtensionsCount = 0;
if (XR_FAILED(xrEnumerateInstanceExtensionPropertiesPtr(nullptr, 0, &ExtensionsCount, nullptr)))
{
UE_LOG(LogPICOOpenXRHMD, Error, TEXT("xrEnumerateInstanceExtensionPropertiesPtr Failed!"));
return false;
}
}
UE_LOG(LogPICOOpenXRHMD, Log, TEXT("Loaded PICO OpenXR Loader"));
bPICORuntime = true;
return true;
#endif //PLATFORM_ANDROID
return false;
}
bool FHMDPICO::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add("XR_FB_display_refresh_rate");
OutExtensions.Add("XR_EXT_local_floor");
OutExtensions.Add("XR_FB_composition_layer_secure_content");
OutExtensions.Add("XR_EXT_performance_settings");
OutExtensions.Add("XR_EXT_user_presence");
2025-07-21 10:22:56 +08:00
OutExtensions.Add("XR_PICO_virtual_boundary");
OutExtensions.Add("XR_PICO_external_camera");
OutExtensions.Add("XR_PICO_layer_settings");
#if UE_VERSION_NEWER_THAN(5, 5, 0)
OutExtensions.Add("XR_PICO_adaptive_resolution");
#endif
OutExtensions.Add("XR_PICO_layer_color_matrix");
if (UPICOOpenXRRuntimeSettings::GetBoolConfigByKey("bEnableFBLayerAlphaBlendExt"))
{
OutExtensions.Add("XR_FB_composition_layer_alpha_blend");
}
2025-03-10 09:43:27 +08:00
return true;
}
void FHMDPICO::PostCreateInstance(XrInstance InInstance)
{
Instance = InInstance;
XrInstanceProperties InstanceProps = { XR_TYPE_INSTANCE_PROPERTIES, nullptr };
XR_ENSURE(xrGetInstanceProperties(InInstance, &InstanceProps));
InstanceProps.runtimeName[XR_MAX_RUNTIME_NAME_SIZE - 1] = 0; // Ensure the name is null terminated.
FString RuntimeName = FString(InstanceProps.runtimeName);
UE_LOG(LogPICOOpenXRHMD, Log, TEXT("PICO OpenXR PostCreateInstance RuntimeName:%s"), *RuntimeName);
}
void FHMDPICO::PostGetSystem(XrInstance InInstance, XrSystemId InSystem)
{
System = InSystem;
bSupportDisplayRefreshRate = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME);
if (bSupportDisplayRefreshRate)
{
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrEnumerateDisplayRefreshRatesFB", (PFN_xrVoidFunction*)&xrEnumerateDisplayRefreshRatesFB));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrGetDisplayRefreshRateFB", (PFN_xrVoidFunction*)&xrGetDisplayRefreshRateFB));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrRequestDisplayRefreshRateFB", (PFN_xrVoidFunction*)&xrRequestDisplayRefreshRateFB));
}
bSupportLocalFloorLevelEXT = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_EXT_LOCAL_FLOOR_EXTENSION_NAME);
bSupportPerformanceSettingsEXT = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME);
if (bSupportPerformanceSettingsEXT)
{
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrPerfSettingsSetPerformanceLevelEXT", (PFN_xrVoidFunction*)&xrPerfSettingsSetPerformanceLevelEXT));
}
if (IOpenXRHMDModule::Get().IsExtensionEnabled(XR_EXT_USER_PRESENCE_EXTENSION_NAME))
{
XrSystemUserPresencePropertiesEXT SystemUserPresenceProperties = { XR_TYPE_SYSTEM_USER_PRESENCE_PROPERTIES_EXT };
XrSystemProperties systemProperties = { XR_TYPE_SYSTEM_PROPERTIES, &SystemUserPresenceProperties };
XR_ENSURE(xrGetSystemProperties(InInstance, System, &systemProperties));
IsSupportsUserPresence = SystemUserPresenceProperties.supportsUserPresence == XR_TRUE;
}
2025-07-21 10:22:56 +08:00
if (IOpenXRHMDModule::Get().IsExtensionEnabled(XR_PICO_VIRTUAL_BOUNDARY_EXTENSION_NAME))
{
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrGetVirtualBoundaryModePICO", (PFN_xrVoidFunction*)&xrGetVirtualBoundaryModePICO));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrGetVirtualBoundaryStatusPICO", (PFN_xrVoidFunction*)&xrGetVirtualBoundaryStatusPICO));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrSetVirtualBoundaryEnablePICO", (PFN_xrVoidFunction*)&xrSetVirtualBoundaryEnablePICO));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrSetVirtualBoundaryVisiblePICO", (PFN_xrVoidFunction*)&xrSetVirtualBoundaryVisiblePICO));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrSetVirtualBoundarySeeThroughVisiblePICO", (PFN_xrVoidFunction*)&xrSetVirtualBoundarySeeThroughVisiblePICO));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrGetVirtualBoundaryTriggerPICO", (PFN_xrVoidFunction*)&xrGetVirtualBoundaryTriggerPICO));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrGetVirtualBoundaryGeometryPICO", (PFN_xrVoidFunction*)&xrGetVirtualBoundaryGeometryPICO));
bSupportedVirtualBoundary = true;
}
bSupportMRCExtension = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_PICO_EXTERNAL_CAMERA_EXTENSION_NAME);
if (bSupportMRCExtension && !MRCDebugMode.EnableExtension)
{
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrGetExternalCameraInfoPICO", (PFN_xrVoidFunction*)&xrGetExternalCameraInfoPICO));
}
bSupportedBDCompositionLayerSettingsExt = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_PICO_LAYER_SETTINGS_EXTENSION_NAME);
2025-03-10 09:43:27 +08:00
2025-07-21 10:22:56 +08:00
bSupportAdaptiveResolution = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_PICO_ADAPTIVE_RESOLUTION_EXTENSION_NAME);
if (bSupportAdaptiveResolution)
{
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrUpdateAdaptiveResolutionPICO", (PFN_xrVoidFunction*)&xrUpdateAdaptiveResolutionPICO));
}
bSupportColorMatrixExtension = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_PICO_LAYER_COLOR_MATRIX_EXTENSION_NAME);
2025-03-10 09:43:27 +08:00
}
const void* FHMDPICO::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext)
{
static FName SystemName(TEXT("OpenXR"));
if (GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName))
{
OpenXRHMD = (FOpenXRHMD*)GEngine->XRSystem.Get();
}
2025-07-21 10:22:56 +08:00
UPICOOpenXRRuntimeSettings* Settings = GetMutableDefault<UPICOOpenXRRuntimeSettings>();
if (Settings)
{
check(Settings != nullptr);
if (Settings->DisableRHIThread)
{
#if PLATFORM_ANDROID
GPendingRHIThreadMode = ERHIThreadMode::None;
#endif //PLATFORM_ANDROID
}
EnableContentProtect(Settings->bContentProtectEXT);
bSupportLayerDepth = Settings->bEnableLayerDepth;
}
2025-03-10 09:43:27 +08:00
return InNext;
}
float ConvertDisplayRefreshRate(EDisplayRefreshRatePICO Rate)
{
switch (Rate)
{
case EDisplayRefreshRatePICO::Default:
return 0.0f;
break;
case EDisplayRefreshRatePICO::Rate72:
return 72.0f;
break;
case EDisplayRefreshRatePICO::Rate90:
return 90.0f;
break;
case EDisplayRefreshRatePICO::Rate120:
return 120.0f;
break;
}
return 0.0f;
}
void FHMDPICO::PostCreateSession(XrSession InSession)
{
FReadScopeLock Lock(SessionHandleMutex);
Session = InSession;
2025-07-21 10:22:56 +08:00
UPICOOpenXRRuntimeSettings* Settings = GetMutableDefault<UPICOOpenXRRuntimeSettings>();
if (Settings && Settings->bDynamicResolution && bSupportAdaptiveResolution)
{
bDynamicResolution = true;
if (IConsoleVariable* MobileDynamicResCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("xr.MobileLDRDynamicResolution")))
{
MobileDynamicResCVar->Set(1);
}
if (IConsoleVariable* DynamicResOperationCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DynamicRes.OperationMode")))
{
DynamicResOperationCVar->Set(2);
}
MinimumResolutionScale = Settings->MinimumDynamicResolutionScale;
CurrentAdaptiveResolutionSetting = Settings->AdaptiveResolutionSetting;
GEngine->ChangeDynamicResolutionStateAtNextFrame(MakeShareable(new FDynamicResolutionStatePICO(this, MinimumResolutionScale)));
}
2025-03-10 09:43:27 +08:00
if (bSupportDisplayRefreshRate)
{
uint32_t DisplayRefreshRateCountOutput = 0;
XR_ENSURE(xrEnumerateDisplayRefreshRatesFB(InSession, 0, &DisplayRefreshRateCountOutput, nullptr));
if (DisplayRefreshRateCountOutput > 0)
{
SupportedDisplayRefreshRates.SetNum(DisplayRefreshRateCountOutput);
XR_ENSURE(xrEnumerateDisplayRefreshRatesFB(InSession, SupportedDisplayRefreshRates.Num(), &DisplayRefreshRateCountOutput, SupportedDisplayRefreshRates.GetData()));
for (int i = 0; i < SupportedDisplayRefreshRates.Num(); i++)
{
UE_LOG(LogPICOOpenXRHMD, Log, TEXT("Supported DisplayRefreshRates[%d]:%f"), i, SupportedDisplayRefreshRates[i]);
}
}
XR_ENSURE(xrGetDisplayRefreshRateFB(InSession, &CurrentDisplayRefreshRate));
UE_LOG(LogPICOOpenXRHMD, Log, TEXT("Get current default DisplayRefreshRate:%f"), CurrentDisplayRefreshRate);
if (Settings)
{
float RequestRate = ConvertDisplayRefreshRate(Settings->DisplayRefreshRate);
SetDisplayRefreshRate(RequestRate);
}
}
if (ViewTrackingSpace == XR_NULL_HANDLE)
{
XrReferenceSpaceCreateInfo SpaceInfo;
SpaceInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
SpaceInfo.next = nullptr;
SpaceInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
SpaceInfo.poseInReferenceSpace = ToXrPose(FTransform::Identity);
XR_ENSURE(xrCreateReferenceSpace(InSession, &SpaceInfo, &ViewTrackingSpace));
}
2025-07-21 10:22:56 +08:00
if (bSupportMRCExtension && MRCSpace == XR_NULL_HANDLE)
{
XrMrcSpaceCreateInfoPICO MRCSpaceCreateInfoBd = { XR_TYPE_MRC_SPACE_CREATE_INFO_PICO };
XrReferenceSpaceCreateInfo SpaceInfo = {};
SpaceInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
SpaceInfo.next = &MRCSpaceCreateInfoBd;
SpaceInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
SpaceInfo.poseInReferenceSpace = ToXrPose(FTransform::Identity);
XR_ENSURE(xrCreateReferenceSpace(InSession, &SpaceInfo, &MRCSpace));
}
if (IConsoleVariable* EnableVariableRateShadingCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.VRS.Enable")))
{
EnableVariableRateShadingCVar->Set(1);
}
if (IConsoleVariable* OpenXRFBFoveationDynamicCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("xr.OpenXRFBFoveationDynamic")))
{
OpenXRFBFoveationDynamicCVar->Set(true);
}
2025-03-10 09:43:27 +08:00
}
void FHMDPICO::OnDestroySession(XrSession InSession)
{
if (ViewTrackingSpace)
{
XR_ENSURE(xrDestroySpace(ViewTrackingSpace));
}
ViewTrackingSpace = XR_NULL_HANDLE;
if (MRCSpace)
{
XR_ENSURE(xrDestroySpace(MRCSpace));
}
MRCSpace = XR_NULL_HANDLE;
}
bool FHMDPICO::UseCustomReferenceSpaceType(XrReferenceSpaceType& OutReferenceSpaceType)
{
if (bSupportLocalFloorLevelEXT)
{
UPICOOpenXRRuntimeSettings* Settings = GetMutableDefault<UPICOOpenXRRuntimeSettings>();
if (Settings && Settings->bLocalFloorLevelEXT)
{
OutReferenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
return true;
}
}
return false;
}
bool FHMDPICO::GetSpectatorScreenController(FHeadMountedDisplayBase* InHMDBase, TUniquePtr<FDefaultSpectatorScreenController>& OutSpectatorScreenController)
{
#if PLATFORM_ANDROID
OutSpectatorScreenController = nullptr;
return true;
#else // PLATFORM_ANDROID
OutSpectatorScreenController = MakeUnique<FDefaultSpectatorScreenController>(InHMDBase);
return false;
#endif // PLATFORM_ANDROID
}
void FHMDPICO::OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader)
{
const XrEventDataBuffer* EventDataBuffer = reinterpret_cast<const XrEventDataBuffer*>(InHeader);
if (EventDataBuffer == nullptr)
{
return;
}
switch (EventDataBuffer->type)
{
case XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB:
if (bSupportDisplayRefreshRate)
{
const XrEventDataDisplayRefreshRateChangedFB* DisplayRefreshRate = reinterpret_cast<const XrEventDataDisplayRefreshRateChangedFB*>(EventDataBuffer);
CurrentDisplayRefreshRate = DisplayRefreshRate->toDisplayRefreshRate;
UHMDFunctionLibraryPICO::GetDelegateManagerPICO()->OnDeviceDisplayRefreshRateChanged.Broadcast(DisplayRefreshRate->fromDisplayRefreshRate, DisplayRefreshRate->toDisplayRefreshRate);
UE_LOG(LogPICOOpenXRHMD, Log, TEXT("DisplayRefreshRate changed from %f to %f."), DisplayRefreshRate->fromDisplayRefreshRate, DisplayRefreshRate->toDisplayRefreshRate);
}
break;
case XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT:
if (bSupportPerformanceSettingsEXT)
{
const XrEventDataPerfSettingsEXT* PerfSettings = reinterpret_cast<const XrEventDataPerfSettingsEXT*>(EventDataBuffer);
UHMDFunctionLibraryPICO::GetDelegateManagerPICO()->OnDevicePerformanceSettingsChanged.Broadcast(EPerfSettingsDomainPICO(PerfSettings->domain)
, EPerfSettingsSubDomainPICO(PerfSettings->subDomain)
, EPerfSettingsNotificationLevelPICO(PerfSettings->toLevel)
, EPerfSettingsNotificationLevelPICO(PerfSettings->fromLevel));
UE_LOG(LogPICOOpenXRHMD, Log, TEXT("PerformanceSettings level changed from %d to %d (domain:%d subdomain:%d"), PerfSettings->fromLevel, PerfSettings->toLevel, PerfSettings->domain, PerfSettings->subDomain);
}
break;
case XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT:
if (IsSupportsUserPresence)
{
const XrEventDataUserPresenceChangedEXT* UserPresenceChanged = reinterpret_cast<const XrEventDataUserPresenceChangedEXT*>(EventDataBuffer);
if (UserPresenceChanged->isUserPresent)
{
WornState = EHMDWornState::Type::Worn;
FCoreDelegates::VRHeadsetPutOnHead.Broadcast();
}
else
{
WornState = EHMDWornState::Type::NotWorn;
FCoreDelegates::VRHeadsetRemovedFromHead.Broadcast();
}
}
break;
2025-07-21 10:22:56 +08:00
case XR_TYPE_EVENT_DATA_MRC_STATUS_CHANGED_PICO:
if (bSupportMRCExtension)
{
const XrEventDataMrcStatusChangedPICO* MRCStatusChanged = reinterpret_cast<const XrEventDataMrcStatusChangedPICO*>(EventDataBuffer);
bIsMRCRunningStored = bIsMRCRunning = MRCStatusChanged->mrcStatus == 1;
UHMDFunctionLibraryPICO::GetDelegateManagerPICO()->OnMRCStatusChanged.Broadcast(bIsMRCRunning);
}
break;
2025-03-10 09:43:27 +08:00
default:
break;
}
}
const void* FHMDPICO::OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags)
{
2025-07-21 10:22:56 +08:00
if (bSupportLayerDepth)
{
OutFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
}
2025-03-10 09:43:27 +08:00
if (bContentProtectEnabled)
{
ContentProtect = { XR_TYPE_COMPOSITION_LAYER_SECURE_CONTENT_FB };
ContentProtect.next = const_cast<void*>(InNext);
ContentProtect.flags = XR_COMPOSITION_LAYER_SECURE_CONTENT_REPLACE_LAYER_BIT_FB;
InNext = &ContentProtect;
}
2025-07-21 10:22:56 +08:00
if (bSupportedBDCompositionLayerSettingsExt)
{
ProjectionLayerSettings = { XR_TYPE_LAYER_SETTINGS_PICO };
static const auto CVarPICOSuperResolution = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.PICO.EnableSuperResolution"));
if (CVarPICOSuperResolution->GetValueOnAnyThread() == 1)
{
ProjectionLayerSettings.layerFlags |= XR_LAYER_SETTINGS_SUPER_RESOLUTION_BIT_PICO;
}
bool bSharpening = false;
static const auto CVarPICOSharpening = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.PICO.SharpeningSetting"));
const ESharpeningTypePICO SharpeningType = static_cast<ESharpeningTypePICO>(CVarPICOSharpening->GetValueOnAnyThread());
switch (SharpeningType)
{
case ESharpeningTypePICO::NormalSharpening:
{
ProjectionLayerSettings.layerFlags |= XR_COMPOSITION_LAYER_SETTINGS_NORMAL_SHARPENING_BIT_FB;
bSharpening = true;
}
break;
case ESharpeningTypePICO::QualitySharpening:
{
ProjectionLayerSettings.layerFlags |= XR_COMPOSITION_LAYER_SETTINGS_QUALITY_SHARPENING_BIT_FB;
bSharpening = true;
}
break;
default:;
}
if (bSharpening)
{
static const auto CVarPICOSharpeningEnhance = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.PICO.SharpeningEnhanceMode"));
const ESharpeningEnhanceModePICO EnhanceMode = static_cast<ESharpeningEnhanceModePICO>(CVarPICOSharpeningEnhance->GetValueOnAnyThread());
switch (EnhanceMode)
{
case ESharpeningEnhanceModePICO::FixedFoveated:
{
ProjectionLayerSettings.layerFlags |= XR_LAYER_SETTINGS_FIXED_FOVEATED_NORMAL_SHARPENING_BIT_PICO;
}
break;
case ESharpeningEnhanceModePICO::Adaptive:
{
ProjectionLayerSettings.layerFlags |= XR_LAYER_SETTINGS_SELF_ADAPTIVE_NORMAL_SHARPENING_BIT_PICO;
}
break;
case ESharpeningEnhanceModePICO::Both:
{
ProjectionLayerSettings.layerFlags |= XR_LAYER_SETTINGS_FIXED_FOVEATED_NORMAL_SHARPENING_BIT_PICO;
ProjectionLayerSettings.layerFlags |= XR_LAYER_SETTINGS_SELF_ADAPTIVE_NORMAL_SHARPENING_BIT_PICO;
}
break;
default:;
}
}
ProjectionLayerSettings.next = const_cast<void*>(InNext);
InNext = &ProjectionLayerSettings;
}
if (bSupportColorMatrixExtension && bUseColorMatrixExtension)
{
XrLayerColorMatrixPICO LayerColorMatrix = { XR_TYPE_LAYER_COLOR_MATRIX_PICO };
FMemory::Memcpy(LayerColorMatrix.matrix.m, ColorMatrix3x3f, 9);
LayerColorMatrix.next = const_cast<void*>(InNext);
InNext = &LayerColorMatrix;
}
2025-03-10 09:43:27 +08:00
return InNext;
}
void FHMDPICO::UpdateDeviceLocations(XrSession InSession, XrTime DisplayTime, XrSpace TrackingSpace)
{
CurrentDisplayTime = DisplayTime;
CurrentBaseSpace = TrackingSpace;
2025-07-21 10:22:56 +08:00
}
2025-03-10 09:43:27 +08:00
2025-07-21 10:22:56 +08:00
void FHMDPICO::OnBeginRendering_GameThread(XrSession InSession)
{
if (bDynamicResolution)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
float NewPixelDensity = 1.0;
float PixelDensity = GetAdaptivePixelDensity(CurrentAdaptiveResolutionSetting, NewPixelDensity) ? NewPixelDensity : 1.0f;
static const auto CVarPICODynamicPixelDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.PICO.DynamicResolution.PixelDensity"));
const float PixelDensityCVarOverride = CVarPICODynamicPixelDensity != nullptr ? CVarPICODynamicPixelDensity->GetValueOnAnyThread() : 0.0f;
if (PixelDensityCVarOverride > 0.0f)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
PixelDensity = PixelDensityCVarOverride;
2025-03-10 09:43:27 +08:00
}
2025-07-21 10:22:56 +08:00
CurrentDynamicPixelDensity = PixelDensity;
2025-03-10 09:43:27 +08:00
}
2025-07-21 10:22:56 +08:00
ENQUEUE_RENDER_COMMAND(UpdateMRCState)(
[this, bIsMRCForegroundLayerDisabled = bIsMRCForegroundLayerDisabled](FRHICommandListImmediate&)
{
bIsMRCForegroundLayerDisabled_RebderThread = bIsMRCForegroundLayerDisabled;
});
2025-03-10 09:43:27 +08:00
}
FIntRect FHMDPICO::GetViewportSize(const FOpenXRLayer::FPerEyeTextureData& EyeData, const IStereoLayers::FLayerDesc& Desc)
{
FBox2D Viewport(EyeData.SwapchainSize * Desc.UVRect.Min, EyeData.SwapchainSize * Desc.UVRect.Max);
return FIntRect(Viewport.Min.IntPoint(), Viewport.Max.IntPoint());
}
FVector2D FHMDPICO::GetQuadSize(const FOpenXRLayer::FPerEyeTextureData& EyeData, const IStereoLayers::FLayerDesc& Desc)
{
if (Desc.Flags & IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO)
{
float AspectRatio = EyeData.SwapchainSize.Y / EyeData.SwapchainSize.X;
return FVector2D(Desc.QuadSize.X, Desc.QuadSize.X * AspectRatio);
}
return Desc.QuadSize;
}
2025-07-21 10:22:56 +08:00
void FHMDPICO::OnWorldTickStart(UWorld* InWorld, ELevelTick TickType, float DeltaTime)
{
if (bSupportMRCExtension && IsInGameThread())
{
if (bIsMRCRunning && !MRCSceneCapture2DPICO)
{
FWorldContext& Context = GEngine->GetWorldContextFromWorldChecked(InWorld);
if (((Context.WorldType == EWorldType::PIE) || (Context.WorldType == EWorldType::Game)) && (Context.World() != NULL))
{
FActorSpawnParameters SpawnInfo;
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnInfo.bNoFail = true;
SpawnInfo.ObjectFlags = RF_Transient;
MRCSceneCapture2DPICO = InWorld->SpawnActor<AMRCCameraPICO>(SpawnInfo);
MRCSceneCapture2DPICO->SetActorEnableCollision(false);
}
}
else if (!bIsMRCRunning && MRCSceneCapture2DPICO)
{
MRCSceneCapture2DPICO->Destroy();
MRCSceneCapture2DPICO = nullptr;
}
}
}
2025-03-10 09:43:27 +08:00
void FHMDPICO::OnBeginRendering_RenderThread(XrSession InSession)
{
if (OpenXRHMD == nullptr || InSession == XR_NULL_HANDLE)
{
return;
}
FXRRenderBridge* RenderBridge = OpenXRHMD->GetActiveRenderBridge_GameThread(OpenXRHMD->ShouldUseSeparateRenderTarget());
FOpenXRRenderBridge* Bridge = static_cast<FOpenXRRenderBridge*>(RenderBridge);
uint8 UnusedActualFormat = 0;
ETextureCreateFlags Flags = TexCreate_Dynamic | TexCreate_SRGB | TexCreate_ShaderResource | TexCreate_RenderTargetable;
FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
static const int64 OPENXR_SWAPCHAIN_WAIT_TIMEOUT = 100000000ll; // 100ms in nanoseconds.
2025-07-21 10:22:56 +08:00
if (bSupportMRCExtension && bIsMRCRunning && !MRCLayer.IsValid())
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
if (Bridge && MRCLayerDesc_RenderThread.Texture.IsValid() && MRCLayerDesc_RenderThread.LeftTexture.IsValid())
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
MRCLayer = MakeShareable(new FOpenXRLayer(MRCLayerDesc_RenderThread));
2025-03-10 09:43:27 +08:00
{
FRHITexture* Texture = MRCLayer->Desc.Texture->GetTexture2D();
FXRSwapChainPtr SwapChain = Bridge->CreateSwapchain(InSession,
IStereoRenderTargetManager::GetStereoLayerPixelFormat(),
UnusedActualFormat,
Texture->GetSizeX(),
Texture->GetSizeY(),
#ifdef PICO_CUSTOM_ENGINE
1,
#endif
1,
Texture->GetNumMips(),
Texture->GetNumSamples(),
Texture->GetFlags() | Flags,
Texture->GetClearBinding());
MRCLayer->RightEye.SetSwapchain(SwapChain, Texture->GetSizeXY());
}
{
FRHITexture* Texture = MRCLayer->Desc.LeftTexture->GetTexture2D();
FXRSwapChainPtr SwapChain = Bridge->CreateSwapchain(InSession,
IStereoRenderTargetManager::GetStereoLayerPixelFormat(),
UnusedActualFormat,
Texture->GetSizeX(),
Texture->GetSizeY(),
#ifdef PICO_CUSTOM_ENGINE
1,
#endif
1,
Texture->GetNumMips(),
Texture->GetNumSamples(),
Texture->GetFlags() | Flags,
Texture->GetClearBinding());
MRCLayer->LeftEye.SetSwapchain(SwapChain, Texture->GetSizeXY());
}
const bool bNoAlpha = MRCLayer->Desc.Flags & IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL;
const bool bIsStereo = MRCLayer->Desc.LeftTexture.IsValid();
const FTransform PositionTransform = FTransform::Identity;
float WorldToMeters = OpenXRHMD->GetWorldToMetersScale();
XrCompositionLayerQuad MRCQuadLayer = { XR_TYPE_COMPOSITION_LAYER_QUAD, nullptr };
MRCQuadLayer.layerFlags = bNoAlpha ? 0 : XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT | XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
2025-07-21 10:22:56 +08:00
MRCQuadLayer.layerFlags |= XR_COMPOSITION_LAYER_MRC_COMPOSITION_BIT_PICO;
2025-03-10 09:43:27 +08:00
MRCQuadLayer.space = ViewTrackingSpace;
MRCQuadLayer.subImage.imageArrayIndex = 0;
MRCQuadLayer.pose = ToXrPose(MRCLayer->Desc.Transform * PositionTransform, WorldToMeters);
const FVector2D LayerComponentScaler(MRCLayer->Desc.Transform.GetScale3D().Y, MRCLayer->Desc.Transform.GetScale3D().Z);
if (MRCLayer->RightEye.Swapchain.IsValid())
{
MRCQuadLayer.eyeVisibility = bIsStereo ? XR_EYE_VISIBILITY_RIGHT : XR_EYE_VISIBILITY_BOTH;
MRCQuadLayer.subImage.imageRect = ToXrRect(GetViewportSize(MRCLayer->RightEye, MRCLayer->Desc));
MRCQuadLayer.subImage.swapchain = static_cast<FOpenXRSwapchain*>(MRCLayer->RightEye.Swapchain.Get())->GetHandle();
MRCQuadLayer.size = ToXrExtent2D(GetQuadSize(MRCLayer->RightEye, MRCLayer->Desc) * LayerComponentScaler, WorldToMeters);
2025-07-21 10:22:56 +08:00
MRCQuadLayerRight_RenderThread = MRCQuadLayer;
2025-03-10 09:43:27 +08:00
}
if (MRCLayer->LeftEye.Swapchain.IsValid())
{
MRCQuadLayer.eyeVisibility = XR_EYE_VISIBILITY_LEFT;
MRCQuadLayer.subImage.imageRect = ToXrRect(GetViewportSize(MRCLayer->LeftEye, MRCLayer->Desc));
MRCQuadLayer.subImage.swapchain = static_cast<FOpenXRSwapchain*>(MRCLayer->LeftEye.Swapchain.Get())->GetHandle();
MRCQuadLayer.size = ToXrExtent2D(GetQuadSize(MRCLayer->LeftEye, MRCLayer->Desc) * LayerComponentScaler, WorldToMeters);
2025-07-21 10:22:56 +08:00
MRCQuadLayerLeft_RenderThread = MRCQuadLayer;
2025-03-10 09:43:27 +08:00
}
}
else
{
MRCLayer.Reset();
}
}
2025-07-21 10:22:56 +08:00
else if (MRCLayer.IsValid() && (!MRCLayerDesc_RenderThread.Texture.IsValid() || !MRCLayerDesc_RenderThread.LeftTexture.IsValid()))
2025-03-10 09:43:27 +08:00
{
MRCLayer.Reset();
}
2025-07-21 10:22:56 +08:00
if (MRCLayer.IsValid())
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
bool bInvertX = MRCDebugMode.ViewInHMD ? true : false;
bool bInvertY = MRCDebugMode.ViewInHMD ? false : true;
2025-03-10 09:43:27 +08:00
2025-07-21 10:22:56 +08:00
if (MRCLayer->RightEye.Swapchain.IsValid())
{
FRHITexture* const RightDstTexture = MRCLayer->RightEye.Swapchain->GetTexture2DArray() ? MRCLayer->RightEye.Swapchain->GetTexture2DArray() : MRCLayer->RightEye.Swapchain->GetTexture2D();
2025-03-10 09:43:27 +08:00
2025-07-21 10:22:56 +08:00
//Right SwapChain
const FXRSwapChainPtr& RightDstSwapChain = MRCLayer->RightEye.Swapchain;
RHICmdList.EnqueueLambda([RightDstSwapChain](FRHICommandListImmediate& InRHICmdList)
{
RightDstSwapChain->IncrementSwapChainIndex_RHIThread();
RightDstSwapChain->WaitCurrentImage_RHIThread(OPENXR_SWAPCHAIN_WAIT_TIMEOUT);
});
if (bIsMRCForegroundLayerDisabled_RebderThread)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
FRHIRenderPassInfo RenderPassInfo(RightDstTexture, ERenderTargetActions::DontLoad_Store);
TransitionRenderPassTargets(RHICmdList, RenderPassInfo);
RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("ClearRT"));
DrawClearQuad(RHICmdList, FLinearColor::Green);
RHICmdList.EndRenderPass();
}
else
{
FRHITexture* RightSrcTexture = MRCLayer->Desc.Texture->GetTexture2D();
const FIntRect RightDstRect(FIntPoint(0, 0), MRCLayer->RightEye.SwapchainSize.IntPoint());
CopyTexture_RenderThread(RHICmdList, RightDstTexture, RightSrcTexture, RightDstRect, FIntRect(), true, true, true, bInvertX, bInvertY);
}
RHICmdList.EnqueueLambda([RightDstSwapChain](FRHICommandListImmediate& InRHICmdList)
{
RightDstSwapChain->ReleaseCurrentImage_RHIThread(nullptr);
});
}
if (MRCLayer->LeftEye.Swapchain.IsValid())
{
//Left SwapChain
const FXRSwapChainPtr& LeftDstSwapChain = MRCLayer->LeftEye.Swapchain;
RHICmdList.EnqueueLambda([LeftDstSwapChain](FRHICommandListImmediate& InRHICmdList)
{
LeftDstSwapChain->IncrementSwapChainIndex_RHIThread();
LeftDstSwapChain->WaitCurrentImage_RHIThread(OPENXR_SWAPCHAIN_WAIT_TIMEOUT);
});
FRHITexture* LeftSrcTexture = MRCLayer->Desc.LeftTexture->GetTexture2D();
const FIntRect LeftDstRect(FIntPoint(0, 0), MRCLayer->LeftEye.SwapchainSize.IntPoint());
FRHITexture* const LeftDstTexture = MRCLayer->LeftEye.Swapchain->GetTexture2DArray() ? MRCLayer->LeftEye.Swapchain->GetTexture2DArray() : MRCLayer->LeftEye.Swapchain->GetTexture2D();
CopyTexture_RenderThread(RHICmdList, LeftDstTexture, LeftSrcTexture, LeftDstRect, FIntRect(), true, true, true/*BG*/, bInvertX, bInvertY);
RHICmdList.EnqueueLambda([LeftDstSwapChain](FRHICommandListImmediate& InRHICmdList)
{
LeftDstSwapChain->ReleaseCurrentImage_RHIThread(nullptr);
});
}
2025-03-10 09:43:27 +08:00
2025-07-21 10:22:56 +08:00
if (MRCDebugMode.ViewInHMD)
{
MRCQuadLayerLeft_RenderThread.layerFlags &= ~XR_COMPOSITION_LAYER_MRC_COMPOSITION_BIT_PICO;
MRCQuadLayerRight_RenderThread.layerFlags &= ~XR_COMPOSITION_LAYER_MRC_COMPOSITION_BIT_PICO;
}
else
{
MRCQuadLayerLeft_RenderThread.layerFlags |= XR_COMPOSITION_LAYER_MRC_COMPOSITION_BIT_PICO;
MRCQuadLayerRight_RenderThread.layerFlags |= XR_COMPOSITION_LAYER_MRC_COMPOSITION_BIT_PICO;
}
2025-03-10 09:43:27 +08:00
2025-07-21 10:22:56 +08:00
RHICmdList.EnqueueLambda([this, MRCQuadLayerLeft_RenderThread = MRCQuadLayerLeft_RenderThread, MRCQuadLayerRight_RenderThread = MRCQuadLayerRight_RenderThread](FRHICommandListImmediate&)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
MRCQuadLayerLeft_RHIThread = MRCQuadLayerLeft_RenderThread;
MRCQuadLayerRight_RHIThread = MRCQuadLayerRight_RenderThread;
2025-03-10 09:43:27 +08:00
});
}
}
void FHMDPICO::UpdateCompositionLayers(XrSession InSession, TArray<const XrCompositionLayerBaseHeader*>& Headers)
{
if (bSupportMRCExtension && MRCLayer.IsValid())
{
2025-07-21 10:22:56 +08:00
Headers.Add(reinterpret_cast<const XrCompositionLayerBaseHeader*>(&MRCQuadLayerLeft_RHIThread));
Headers.Add(reinterpret_cast<const XrCompositionLayerBaseHeader*>(&MRCQuadLayerRight_RHIThread));
2025-03-10 09:43:27 +08:00
}
}
void FHMDPICO::UpdateCompositionLayers(XrSession InSession, TArray<XrCompositionLayerBaseHeader*>& Headers)
{
2025-07-21 10:22:56 +08:00
if (bSupportLayerDepth)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
#ifndef PICO_CUSTOM_ENGINE
//Stereo Layer default support depth
for (int32 i = 0; i < Headers.Num(); i++)
{
2025-07-21 10:22:56 +08:00
if (OpenXRHMD && OpenXRHMD->GetTrackedDeviceSpace(IXRTrackingSystem::HMDDeviceId) == Headers[i]->space)
{
continue;//skip face-lock layer
}
2025-07-21 10:22:56 +08:00
if (Headers[i]->type != XR_TYPE_COMPOSITION_LAYER_PROJECTION && Headers[i]->type != XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB)
{
BlendState.next = const_cast<void*>(Headers[i]->next);
Headers[i]->next = &BlendState;
}
2025-03-10 09:43:27 +08:00
}
#endif // PICO_CUSTOM_ENGINE
2025-07-21 10:22:56 +08:00
}
2025-03-10 09:43:27 +08:00
if (bSupportMRCExtension && MRCLayer.IsValid())
{
2025-07-21 10:22:56 +08:00
Headers.Add(reinterpret_cast<XrCompositionLayerBaseHeader*>(&MRCQuadLayerLeft_RHIThread));
Headers.Add(reinterpret_cast<XrCompositionLayerBaseHeader*>(&MRCQuadLayerRight_RHIThread));
2025-03-10 09:43:27 +08:00
}
}
bool FHMDPICO::GetSupportedDisplayRefreshRates(TArray<float>& Rates)
{
Rates = SupportedDisplayRefreshRates;
return bSupportDisplayRefreshRate;
}
2025-07-21 10:22:56 +08:00
bool FHMDPICO::GetCurrentDisplayRefreshRate(float& Rate, bool DoubleCheck)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
if (bSupportDisplayRefreshRate)
{
if (DoubleCheck && Session)
{
float DisplayRefreshRate = 0;
if (XR_SUCCEEDED(xrGetDisplayRefreshRateFB(Session, &DisplayRefreshRate)))
{
if (DisplayRefreshRate == CurrentDisplayRefreshRate) // Check XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB
{
Rate = DisplayRefreshRate;
return true;
}
}
}
else
{
Rate = CurrentDisplayRefreshRate;
return true;
}
}
return false;
2025-03-10 09:43:27 +08:00
}
bool FHMDPICO::SetDisplayRefreshRate(float Rate)
{
FReadScopeLock Lock(SessionHandleMutex);
if (bSupportDisplayRefreshRate && Session)
{
if (SupportedDisplayRefreshRates.Contains(Rate))
{
UE_LOG(LogPICOOpenXRHMD, Log, TEXT("Requesting DisplayRefreshRate:%f"), Rate);
XR_ENSURE(xrRequestDisplayRefreshRateFB(Session, Rate));
return true;
}
else
{
UE_LOG(LogPICOOpenXRHMD, Warning, TEXT("Requested DisplayRefreshRate:%f is not supported by device!"), Rate);
}
}
return false;
}
void FHMDPICO::EnableContentProtect(bool Enable)
{
bool ContentProtectSupportedEXT = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_FB_COMPOSITION_LAYER_SECURE_CONTENT_EXTENSION_NAME);
bContentProtectEnabled = Enable && ContentProtectSupportedEXT;
UE_LOG(LogPICOOpenXRHMD, Log, TEXT(":EnableContentProtect EXT Supported:%d, Enabled:%d"), ContentProtectSupportedEXT, bContentProtectEnabled);
}
bool FHMDPICO::SetPerformanceLevel(int domain, int level)
{
if (Session && bSupportPerformanceSettingsEXT)
{
return XR_SUCCEEDED(xrPerfSettingsSetPerformanceLevelEXT(Session, XrPerfSettingsDomainEXT(domain), XrPerfSettingsLevelEXT(level)));
}
return false;
}
bool FHMDPICO::GetDevicePoseForTime(const EControllerHand Hand, bool UseDefaultTime, FTimespan Timespan, bool& OutTimeWasUsed, FRotator& OutOrientation, FVector& OutPosition, bool& OutbProvidedLinearVelocity, FVector& OutLinearVelocity, bool& OutbProvidedAngularVelocity, FVector& OutAngularVelocityRadPerSec, bool& OutbProvidedLinearAcceleration, FVector& OutLinearAcceleration, float InWorldToMetersScale)
{
if (OpenXRHMD == nullptr)
{
return false;
}
int32 DeviceId = -1;
if (Hand == EControllerHand::HMD)
{
DeviceId = IXRTrackingSystem::HMDDeviceId;
}
else
{
TArray<int32> Devices;
if (OpenXRHMD->EnumerateTrackedDevices(Devices, EXRTrackedDeviceType::Controller))
{
if (Devices.IsValidIndex((int32)Hand))
{
DeviceId = Devices[(int32)Hand];
}
}
}
if (DeviceId == -1)
{
return false;
}
XrTime TargetTime = 0;
if (UseDefaultTime)
{
TargetTime = CurrentDisplayTime;
}
else
{
TargetTime = ToXrTime(Timespan);
}
if (TargetTime == 0)
{
OutTimeWasUsed = false;
}
else
{
OutTimeWasUsed = true;
}
FQuat Orientation;
bool Success = OpenXRHMD->GetPoseForTime(DeviceId, TargetTime, OutTimeWasUsed, Orientation, OutPosition, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityRadPerSec, OutbProvidedLinearAcceleration, OutLinearAcceleration, InWorldToMetersScale);
OutOrientation = FRotator(Orientation);
return Success;
}
EHMDWornState::Type FHMDPICO::GetHMDWornState(bool& ResultValid)
{
ResultValid = IsSupportsUserPresence;
return WornState;
}
bool FHMDPICO::GetFieldOfView(float& OutHFOVInDegrees, float& OutVFOVInDegrees)
{
if (OpenXRHMD)
{
OpenXRHMD->GetHMDDevice()->GetFieldOfView(OutHFOVInDegrees, OutVFOVInDegrees);
return true;
}
return false;
}
bool FHMDPICO::GetInterpupillaryDistance(float& IPD)
{
if (OpenXRHMD)
{
IPD = OpenXRHMD->GetInterpupillaryDistance() * OpenXRHMD->GetWorldToMetersScale();
return true;
}
return false;
}
void FHMDPICO::SetBaseRotationAndBaseOffset(FRotator Rotation, FVector BaseOffset, EOrientPositionSelector::Type Options)
{
if (OpenXRHMD)
{
if ((Options == EOrientPositionSelector::Orientation) || (Options == EOrientPositionSelector::OrientationAndPosition))
{
OpenXRHMD->SetBaseRotation(Rotation);
}
if ((Options == EOrientPositionSelector::Position) || (Options == EOrientPositionSelector::OrientationAndPosition))
{
OpenXRHMD->SetBasePosition(BaseOffset);
}
}
}
void FHMDPICO::GetBaseRotationAndBaseOffset(FRotator& OutRotation, FVector& OutBaseOffset)
{
if (OpenXRHMD != nullptr)
{
OutRotation = OpenXRHMD->GetBaseRotation();
OutBaseOffset = OpenXRHMD->GetBasePosition();
}
else
{
OutRotation = FRotator::ZeroRotator;
OutBaseOffset = FVector::ZeroVector;
}
}
FTimespan FHMDPICO::GetDisplayTime()
{
return ToFTimespan(CurrentDisplayTime);
}
2025-07-21 10:22:56 +08:00
bool FHMDPICO::IsStationaryBoundaryMode(bool& bIsStationary)
{
if (Session && bSupportedVirtualBoundary)
{
XrVirtualBoundaryModePICO Mode;
if (XR_SUCCEEDED(xrGetVirtualBoundaryModePICO(Session, &Mode)))
{
bIsStationary = Mode == XrVirtualBoundaryModePICO::XR_VIRTUAL_BOUNDARY_MODE_STATIONARY_PICO;
return true;
}
}
return false;
}
bool FHMDPICO::GetVirtualBoundaryStatus(bool& bIsReady, bool& bIsEnable, bool& bIsVisible)
{
if (Session && bSupportedVirtualBoundary)
{
XrVirtualBoundaryStatusPICO Status = { XR_TYPE_VIRTUAL_BOUNDARY_STATUS_PICO };
if (XR_SUCCEEDED(xrGetVirtualBoundaryStatusPICO(Session, &Status)))
{
bIsReady = Status.isReady == XR_TRUE;
bIsEnable = Status.isEnabled == XR_TRUE;
bIsVisible = Status.isVisible == XR_TRUE;
return true;
}
}
return false;
}
bool FHMDPICO::SetVirtualBoundaryEnable(bool bEnable)
{
if (Session && bSupportedVirtualBoundary)
{
if (XR_SUCCEEDED(xrSetVirtualBoundaryEnablePICO(Session, bEnable)))
{
return true;
}
}
return false;
}
bool FHMDPICO::SetVirtualBoundaryVisible(bool bVisible)
{
if (Session && bSupportedVirtualBoundary)
{
if (XR_SUCCEEDED(xrSetVirtualBoundaryVisiblePICO(Session, bVisible)))
{
return true;
}
}
return false;
}
bool FHMDPICO::SetVirtualBoundarySeeThroughVisible(bool bVisible)
{
if (Session && bSupportedVirtualBoundary)
{
if (XR_SUCCEEDED(xrSetVirtualBoundarySeeThroughVisiblePICO(Session, bVisible)))
{
return true;
}
}
return false;
}
bool FHMDPICO::BoundaryintersectPointOrNode(bool bPoint, EControllerHand Node, FVector Point, EBoundaryTypePICO BoundaryType, bool& Valid, bool& IsTriggering, float& ClosestDistance, FVector& ClosestPoint, FVector& ClosestPointNormal, float InWorldToMetersScale)
{
if (Session && bSupportedVirtualBoundary)
{
XrVirtualBoundaryInfoPICO VirtualBoundaryInfo = { XR_TYPE_VIRTUAL_BOUNDARY_INFO_PICO };
VirtualBoundaryInfo.baseSpace = CurrentBaseSpace;
VirtualBoundaryInfo.edgeType = (XrVirtualBoundaryEdgeTypePICO)BoundaryType;
XrVirtualBoundaryTriggerPointPICO BoundaryTestPoint = { XR_TYPE_VIRTUAL_BOUNDARY_TRIGGER_POINT_PICO };
BoundaryTestPoint.point = ToXrVector(Point, InWorldToMetersScale);
BoundaryTestPoint.boundaryInfo = &VirtualBoundaryInfo;
XrVirtualBoundaryTriggerNodePICO VirtualBoundaryTriggerNode = { XR_TYPE_VIRTUAL_BOUNDARY_TRIGGER_NODE_PICO };
VirtualBoundaryTriggerNode.boundaryInfo = &VirtualBoundaryInfo;
switch (Node)
{
case EControllerHand::Left:
VirtualBoundaryTriggerNode.node = XR_VIRTUAL_BOUNDARY_TRIGGER_NODE_TYPE_LEFT_HAND_PICO;
break;
case EControllerHand::Right:
VirtualBoundaryTriggerNode.node = XR_VIRTUAL_BOUNDARY_TRIGGER_NODE_TYPE_RIGHT_HAND_PICO;
break;
case EControllerHand::HMD:
VirtualBoundaryTriggerNode.node = XR_VIRTUAL_BOUNDARY_TRIGGER_NODE_TYPE_HEAD_PICO;
break;
default:
break;
}
const XrVirtualBoundaryTriggerBaseHeaderPICO* BaseHeader = nullptr;
if (bPoint)
{
BaseHeader = reinterpret_cast<const XrVirtualBoundaryTriggerBaseHeaderPICO*>(&BoundaryTestPoint);
}
else
{
BaseHeader = reinterpret_cast<const XrVirtualBoundaryTriggerBaseHeaderPICO*>(&VirtualBoundaryTriggerNode);
}
XrVirtualBoundaryTriggerPICO VirtualBoundaryTrigger = { XR_TYPE_VIRTUAL_BOUNDARY_TRIGGER_PICO };
if (XR_SUCCEEDED(xrGetVirtualBoundaryTriggerPICO(Session, BaseHeader, &VirtualBoundaryTrigger)))
{
Valid = VirtualBoundaryTrigger.isValid == XR_TRUE;
ClosestDistance = VirtualBoundaryTrigger.closestDistance * InWorldToMetersScale;
IsTriggering = VirtualBoundaryTrigger.isTriggering == XR_TRUE;
ClosestPoint = ToFVector(VirtualBoundaryTrigger.closestPoint, InWorldToMetersScale);
ClosestPointNormal = ToFVector(VirtualBoundaryTrigger.closestPointNormal);
return true;
}
}
return false;
}
bool FHMDPICO::GetBoundaryGeometry(EBoundaryTypePICO BoundaryType, bool& Valid, TArray<FVector>& Points, float InWorldToMetersScale)
{
if (Session && bSupportedVirtualBoundary)
{
XrVirtualBoundaryInfoPICO VirtualBoundaryInfo = { XR_TYPE_VIRTUAL_BOUNDARY_INFO_PICO };
VirtualBoundaryInfo.baseSpace = CurrentBaseSpace;
VirtualBoundaryInfo.edgeType = (XrVirtualBoundaryEdgeTypePICO)BoundaryType;
uint32 count = 0;
xrGetVirtualBoundaryGeometryPICO(Session, &VirtualBoundaryInfo, 0, &count, nullptr);
if (count > 0)
{
TArray<XrVector3f> xrPoints;
Points.SetNum(count);
xrPoints.SetNum(count);
if (XR_SUCCEEDED(xrGetVirtualBoundaryGeometryPICO(Session, &VirtualBoundaryInfo, count, &count, xrPoints.GetData())))
{
for (int i = 0; i < xrPoints.Num(); i++)
{
Points[i] = ToFVector(xrPoints[i], InWorldToMetersScale);
}
return true;
}
}
}
return false;
}
void FHMDPICO::CopyTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture* DstTexture, FRHITexture* SrcTexture, FIntRect DstRect, FIntRect SrcRect, bool bAlphaPremultiply, bool bNoAlpha, bool bClearGreen, bool bInvertX, bool bInvertY, bool bInvertAlpha) const
2025-03-10 09:43:27 +08:00
{
ERenderTargetActions RTAction = ERenderTargetActions::Clear_Store;
ERHIAccess FinalDstAccess = ERHIAccess::SRVMask;
check(IsInRenderingThread());
const uint32 ViewportWidth = DstRect.Width();
const uint32 ViewportHeight = DstRect.Height();
const FIntPoint TargetSize(ViewportWidth, ViewportHeight);
const float SrcTextureWidth = SrcTexture->GetSizeX();
const float SrcTextureHeight = SrcTexture->GetSizeY();
float U = 0.f, V = 0.f, USize = 1.f, VSize = 1.f;
if (SrcRect.IsEmpty())
{
SrcRect.Min.X = 0;
SrcRect.Min.Y = 0;
SrcRect.Max.X = SrcTextureWidth;
SrcRect.Max.Y = SrcTextureHeight;
}
else
{
U = SrcRect.Min.X / SrcTextureWidth;
V = SrcRect.Min.Y / SrcTextureHeight;
USize = SrcRect.Width() / SrcTextureWidth;
VSize = SrcRect.Height() / SrcTextureHeight;
}
2025-07-21 10:22:56 +08:00
if (bInvertX)
{
U = 1.0f - U;
USize = -USize;
}
2025-03-10 09:43:27 +08:00
if (bInvertY)
{
V = 1.0f - V;
VSize = -VSize;
}
RHICmdList.Transition(FRHITransitionInfo(DstTexture, ERHIAccess::Unknown, ERHIAccess::RTV));
FRHITexture* ColorRT = DstTexture->GetTexture2DArray() ? DstTexture->GetTexture2DArray() : DstTexture->GetTexture2D();
FRHIRenderPassInfo RenderPassInfo(ColorRT, RTAction);
RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("OpenXRHMD_CopyTexture"));
{
if (bClearGreen || bNoAlpha)
{
const FIntRect ClearRect(0, 0, DstTexture->GetSizeX(), DstTexture->GetSizeY());
RHICmdList.SetViewport(ClearRect.Min.X, ClearRect.Min.Y, 0, ClearRect.Max.X, ClearRect.Max.Y, 1.0f);
if (bClearGreen)
{
DrawClearQuad(RHICmdList, FLinearColor::Green);
}
else
{
// For opaque texture copies, we want to make sure alpha is initialized to 1.0f
DrawClearQuadAlpha(RHICmdList, 1.0f);
}
}
RHICmdList.SetViewport(DstRect.Min.X, DstRect.Min.Y, 0, DstRect.Max.X, DstRect.Max.Y, 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
if (bClearGreen)
{
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_InverseSourceAlpha, BF_SourceAlpha, BO_Add, BF_Zero, BF_InverseSourceAlpha>::GetRHI();
}
else if (bInvertAlpha)
{
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_Zero, BO_Add, BF_Zero, BF_InverseSourceAlpha >::GetRHI();
}
else if (bAlphaPremultiply)
{
if (bNoAlpha)
{
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI();
}
else
{
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_SourceAlpha, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI();
}
}
else
{
if (bNoAlpha)
{
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB>::GetRHI();
}
else
{
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha, BO_Add, BF_One, BF_InverseSourceAlpha>::GetRHI();
}
}
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(OpenXRHMD->GetConfiguredShaderPlatform());
TShaderMapRef<FScreenVS> VertexShader(ShaderMap);
TShaderRef<FGlobalShader> PixelShader;
TShaderRef<FDisplayMappingPixelShader> DisplayMappingPS;
TShaderRef<FScreenPS> ScreenPS;
bool bNeedsDisplayMapping = false;
bool bIsInputLinear = false;
EDisplayOutputFormat TVDisplayOutputFormat = EDisplayOutputFormat::SDR_sRGB;
EDisplayColorGamut HMDColorGamut = EDisplayColorGamut::sRGB_D65;
EDisplayColorGamut TVColorGamut = EDisplayColorGamut::sRGB_D65;
FOpenXRRenderBridge* RenderBridge = static_cast<FOpenXRRenderBridge*>(OpenXRHMD->GetActiveRenderBridge_GameThread(OpenXRHMD->ShouldUseSeparateRenderTarget()));
if (FinalDstAccess == ERHIAccess::Present && RenderBridge)
{
EDisplayOutputFormat HMDDisplayFormat;
bool bHMDSupportHDR;
if (RenderBridge->HDRGetMetaDataForStereo(HMDDisplayFormat, HMDColorGamut, bHMDSupportHDR))
{
bool bTVSupportHDR;
HDRGetMetaData(TVDisplayOutputFormat, TVColorGamut, bTVSupportHDR, FVector2D(0, 0), FVector2D(0, 0), nullptr);
if (TVDisplayOutputFormat != HMDDisplayFormat || HMDColorGamut != TVColorGamut || bTVSupportHDR != bHMDSupportHDR)
{
// shader assumes G 2.2 for input / ST2084/sRGB for output right now
ensure(HMDDisplayFormat == EDisplayOutputFormat::SDR_ExplicitGammaMapping);
ensure(TVDisplayOutputFormat == EDisplayOutputFormat::SDR_sRGB || TVDisplayOutputFormat == EDisplayOutputFormat::HDR_ACES_1000nit_ST2084 || TVDisplayOutputFormat == EDisplayOutputFormat::HDR_ACES_2000nit_ST2084);
bNeedsDisplayMapping = true;
}
}
// In Android Vulkan preview, when the sRGB swapchain texture is sampled, the data is converted to linear and written to the RGBA10A2_UNORM texture.
// However, D3D interprets integer-valued display formats as containing sRGB data, so we need to convert the linear data back to sRGB.
if (!IsMobileHDR() && IsMobilePlatform(OpenXRHMD->GetConfiguredShaderPlatform()) && IsSimulatedPlatform(OpenXRHMD->GetConfiguredShaderPlatform()))
{
bNeedsDisplayMapping = true;
TVDisplayOutputFormat = EDisplayOutputFormat::SDR_sRGB;
bIsInputLinear = true;
}
}
bNeedsDisplayMapping &= IsFeatureLevelSupported(OpenXRHMD->GetConfiguredShaderPlatform(), ERHIFeatureLevel::ES3_1);
bool bIsArraySource = SrcTexture->GetDesc().IsTextureArray();
if (bNeedsDisplayMapping)
{
FDisplayMappingPixelShader::FPermutationDomain PermutationVector;
PermutationVector.Set<FDisplayMappingPixelShader::FArraySource>(bIsArraySource);
PermutationVector.Set<FDisplayMappingPixelShader::FLinearInput>(bIsInputLinear);
TShaderMapRef<FDisplayMappingPixelShader> DisplayMappingPSRef(ShaderMap, PermutationVector);
DisplayMappingPS = DisplayMappingPSRef;
PixelShader = DisplayMappingPSRef;
}
else
{
if (LIKELY(!bIsArraySource))
{
TShaderMapRef<FScreenPS> ScreenPSRef(ShaderMap);
ScreenPS = ScreenPSRef;
PixelShader = ScreenPSRef;
}
else
{
TShaderMapRef<FScreenFromSlice0PS> ScreenPSRef(ShaderMap);
ScreenPS = ScreenPSRef;
PixelShader = ScreenPSRef;
}
}
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
RHICmdList.Transition(FRHITransitionInfo(SrcTexture, ERHIAccess::Unknown, ERHIAccess::SRVMask));
const bool bSameSize = DstRect.Size() == SrcRect.Size();
if (ScreenPS.IsValid())
{
FRHISamplerState* PixelSampler = bSameSize ? TStaticSamplerState<SF_Point>::GetRHI() : TStaticSamplerState<SF_Bilinear>::GetRHI();
SetShaderParametersLegacyPS(RHICmdList, ScreenPS, PixelSampler, SrcTexture);
}
else if (DisplayMappingPS.IsValid())
{
SetShaderParametersLegacyPS(RHICmdList, DisplayMappingPS, TVDisplayOutputFormat, TVColorGamut, HMDColorGamut, SrcTexture, bSameSize);
}
FModuleManager::GetModulePtr<IRendererModule>("Renderer")->DrawRectangle(
RHICmdList,
0, 0,
ViewportWidth, ViewportHeight,
U, V,
USize, VSize,
TargetSize,
FIntPoint(1, 1),
VertexShader,
EDRF_Default);
}
RHICmdList.EndRenderPass();
RHICmdList.Transition(FRHITransitionInfo(DstTexture, ERHIAccess::RTV, FinalDstAccess));
}
void FHMDPICO::CreateMRCLayer(class UTexture* BackgroundRTTexture, class UTexture* ForegroundRTTexture)
{
2025-07-21 10:22:56 +08:00
ENQUEUE_RENDER_COMMAND(CreateMRCLayer)(
[this, BackgroundRTTexture = BackgroundRTTexture, ForegroundRTTexture = ForegroundRTTexture](FRHICommandListImmediate&)
{
if (BackgroundRTTexture && BackgroundRTTexture->GetResource() && ForegroundRTTexture && ForegroundRTTexture->GetResource())
{
MRCLayerDesc_RenderThread.PositionType = IStereoLayers::ELayerType::FaceLocked;
MRCLayerDesc_RenderThread.Transform.SetLocation(FVector(100, 0, 0));
MRCLayerDesc_RenderThread.Texture = ForegroundRTTexture->GetResource()->TextureRHI;
MRCLayerDesc_RenderThread.LeftTexture = BackgroundRTTexture->GetResource()->TextureRHI;
MRCLayerDesc_RenderThread.Flags = IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_CONTINUOUS_UPDATE | IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL;
MRCLayerDesc_RenderThread.QuadSize = FVector2D(100, 100);
}
});
2025-03-10 09:43:27 +08:00
}
void FHMDPICO::DestroyMRCLayer()
{
2025-07-21 10:22:56 +08:00
ENQUEUE_RENDER_COMMAND(DestroyMRCLayer)(
[this](FRHICommandListImmediate&)
{
MRCLayerDesc_RenderThread.Texture = nullptr;
MRCLayerDesc_RenderThread.LeftTexture = nullptr;
MRCLayer.Reset();
});
MRCSceneCapture2DPICO = nullptr;
2025-03-10 09:43:27 +08:00
}
bool FHMDPICO::GetExternalCameraInfo(int32& width, int32& height, float& fov)
{
if (MRCDebugMode.UseCustomCameraInfo)
{
width = MRCDebugMode.Width;
height = MRCDebugMode.Height;
fov = MRCDebugMode.Fov;
return true;
}
2025-07-21 10:22:56 +08:00
if (Session && bSupportMRCExtension && xrGetExternalCameraInfoPICO)
{
XrExternalCameraParameterPICO ExternalCameraInfo = { XR_TYPE_EXTERNAL_CAMERA_PARAMETER_PICO };
if (XR_SUCCEEDED(xrGetExternalCameraInfoPICO(Session, &ExternalCameraInfo)))
{
width = ExternalCameraInfo.width;
height = ExternalCameraInfo.height;
fov = ExternalCameraInfo.fov;
return true;
}
}
2025-03-10 09:43:27 +08:00
return false;
}
bool FHMDPICO::GetExternalCameraPose(FTransform& Pose)
{
if (MRCDebugMode.UseCustomTransform)
{
Pose = MRCDebugMode.Pose;
return true;
}
if (bSupportMRCExtension && Session && CurrentBaseSpace && MRCSpace && OpenXRHMD)
{
XrSpaceLocation NewLocation = { XR_TYPE_SPACE_LOCATION };
const XrResult Result = xrLocateSpace(MRCSpace, CurrentBaseSpace, CurrentDisplayTime, &NewLocation);
if (Result != XR_SUCCESS)
{
return false;
}
if (!(NewLocation.locationFlags & (XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)))
{
return false;
}
const FQuat Orientation = ToFQuat(NewLocation.pose.orientation);
const FVector Position = ToFVector(NewLocation.pose.position, OpenXRHMD->GetWorldToMetersScale());
FTransform TrackingToWorld = OpenXRHMD->GetTrackingToWorldTransform();
Pose = FTransform(Orientation, Position) * TrackingToWorld;
return true;
}
return false;
}
void FHMDPICO::EnableMRCDebugMode(class UWorld* WorldContext, bool Enable, bool ViewInHMD, bool UseCustomTransform, const FTransform& Pose, bool UseCustomCameraInfo, int Width, int Height, float Fov)
{
bSupportMRCExtension = Enable;
bIsMRCRunning |= Enable;
2025-07-21 10:22:56 +08:00
bIsMRCRunningStored |= Enable;
2025-03-10 09:43:27 +08:00
if (Enable)
{
MRCDebugMode.EnableExtension = Enable;
MRCDebugMode.ViewInHMD = ViewInHMD;
MRCDebugMode.UseCustomTransform = UseCustomTransform;
MRCDebugMode.Pose = Pose;
MRCDebugMode.UseCustomCameraInfo = UseCustomCameraInfo;
MRCDebugMode.Width = Width;
MRCDebugMode.Height = Height;
MRCDebugMode.Fov = Fov;
}
else
{
MRCDebugMode = {};
}
}
2025-07-21 10:22:56 +08:00
void FHMDPICO::DisableMRCForegroundLayer(UObject* WorldContextObject, bool Disable)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
bIsMRCForegroundLayerDisabled = Disable;
if (MRCSceneCapture2DPICO)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
MRCSceneCapture2DPICO->DisableForegroundLayer = bIsMRCForegroundLayerDisabled;
}
}
2025-03-10 09:43:27 +08:00
2025-07-21 10:22:56 +08:00
void FHMDPICO::PauseMRC(bool Pause)
{
if (Pause)
{
if (bIsMRCRunning)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
bIsMRCRunning = false;
2025-03-10 09:43:27 +08:00
}
}
else
{
2025-07-21 10:22:56 +08:00
if (bIsMRCRunningStored && !bIsMRCRunning)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
bIsMRCRunning = true;
2025-03-10 09:43:27 +08:00
}
}
}
2025-07-21 10:22:56 +08:00
bool FHMDPICO::GetAdaptivePixelDensity(EAdaptiveResolutionSettingPICO Setting, float& PixelDensity)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
if (OpenXRHMD && bSupportAdaptiveResolution && Session)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
uint32 SizeX, SizeY;
GetCurrentRenderTargetSize(SizeX, SizeY);
FIntPoint ViewportSize =
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
FMath::CeilToInt(SizeX * CurrentDynamicPixelDensity),
FMath::CeilToInt(SizeY * CurrentDynamicPixelDensity)
};
QuantizeSceneBufferSize(ViewportSize, ViewportSize);
2025-03-10 09:43:27 +08:00
2025-07-21 10:22:56 +08:00
XrExtent2Di Extent2D = { ViewportSize.X, ViewportSize.Y };
if (XR_SUCCEEDED(xrUpdateAdaptiveResolutionPICO(Session, (XrAdaptiveResolutionSettingPICO)Setting, Extent2D, &Extent2D)))
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
PixelDensity = (float)Extent2D.height / (float)SizeY;
return true;
2025-03-10 09:43:27 +08:00
}
}
2025-07-21 10:22:56 +08:00
return false;
2025-03-10 09:43:27 +08:00
}
FIntPoint FHMDPICO::GetDefaultRenderTargetSize()
{
if (OpenXRHMD)
{
return OpenXRHMD->GetIdealRenderTargetSize();
}
return FIntPoint();
}
void FHMDPICO::GetCurrentRenderTargetSize(uint32& InOutSizeX, uint32& InOutSizeY)
{
InOutSizeX = InOutSizeY = 0;
if (OpenXRHMD && GEngine)
{
check(GEngine->GameViewport->Viewport);
OpenXRHMD->CalculateRenderTargetSize(*GEngine->GameViewport->Viewport, InOutSizeX, InOutSizeY);
}
}
2025-07-21 10:22:56 +08:00
bool FHMDPICO::SetProjectionLayerColorMatrix3x3f(bool Enable, FVector3f ColumnA, FVector3f ColumnB, FVector3f ColumnC)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
if (bSupportColorMatrixExtension)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
bUseColorMatrixExtension = Enable;
if (bUseColorMatrixExtension)
{
ColorMatrix3x3f[0] = ColumnA.X;
ColorMatrix3x3f[1] = ColumnA.Y;
ColorMatrix3x3f[2] = ColumnA.Z;
ColorMatrix3x3f[3] = ColumnB.X;
ColorMatrix3x3f[4] = ColumnB.Y;
ColorMatrix3x3f[5] = ColumnB.Z;
ColorMatrix3x3f[6] = ColumnC.X;
ColorMatrix3x3f[7] = ColumnC.Y;
ColorMatrix3x3f[8] = ColumnC.Z;
}
return true;
2025-03-10 09:43:27 +08:00
}
2025-07-21 10:22:56 +08:00
return false;
2025-03-10 09:43:27 +08:00
}
2025-07-21 10:22:56 +08:00
bool FHMDPICO::GetViewportSize(FIntPoint& ViewportSize)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
if (OpenXRHMD && bSupportAdaptiveResolution && Session)
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
uint32 SizeX, SizeY;
GetCurrentRenderTargetSize(SizeX, SizeY);
float PixelDensity = FMath::Clamp(CurrentDynamicPixelDensity, MinimumResolutionScale, 1.0f);
ViewportSize =
2025-03-10 09:43:27 +08:00
{
2025-07-21 10:22:56 +08:00
FMath::CeilToInt(SizeX * PixelDensity),
FMath::CeilToInt(SizeY * PixelDensity)
};
QuantizeSceneBufferSize(ViewportSize, ViewportSize);
return true;
2025-03-10 09:43:27 +08:00
}
2025-07-21 10:22:56 +08:00
return false;
2025-03-10 09:43:27 +08:00
}