1259 lines
42 KiB
C++
1259 lines
42 KiB
C++
// 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"
|
|
|
|
#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"),
|
|
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);
|
|
|
|
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)
|
|
{
|
|
SupportedDisplayRefreshRates.Empty();
|
|
}
|
|
|
|
void FHMDPICO::Register()
|
|
{
|
|
RegisterOpenXRExtensionModularFeature();
|
|
WorldLoadDelegate = FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw(this, &FHMDPICO::OnWorldCreated);
|
|
}
|
|
|
|
void FHMDPICO::Unregister()
|
|
{
|
|
UnregisterOpenXRExtensionModularFeature();
|
|
if (LoaderHandle)
|
|
{
|
|
FPlatformProcess::FreeDllHandle(LoaderHandle);
|
|
LoaderHandle = nullptr;
|
|
}
|
|
|
|
if (WorldLoadDelegate.IsValid())
|
|
{
|
|
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(WorldLoadDelegate);
|
|
WorldLoadDelegate.Reset();
|
|
}
|
|
}
|
|
|
|
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");
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool FHMDPICO::IsStandaloneStereoOnlyDevice()
|
|
{
|
|
#if PLATFORM_ANDROID
|
|
FString DeviceMake = FAndroidMisc::GetDeviceMake().ToLower();
|
|
UE_LOG(LogPICOOpenXRHMD, Verbose, TEXT("DeviceMake:%s"), *DeviceMake);
|
|
return DeviceMake == FString("pico");
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
EnableContentProtect(GetMutableDefault<UPICOOpenXRRuntimeSettings>()->bContentProtectEXT);
|
|
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;
|
|
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);
|
|
|
|
UPICOOpenXRRuntimeSettings* Settings = GetMutableDefault<UPICOOpenXRRuntimeSettings>();
|
|
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));
|
|
}
|
|
}
|
|
|
|
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;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const void* FHMDPICO::OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags)
|
|
{
|
|
OutFlags |= XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
|
|
OutFlags |= XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
|
|
|
|
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;
|
|
}
|
|
|
|
return InNext;
|
|
}
|
|
|
|
void FHMDPICO::UpdateDeviceLocations(XrSession InSession, XrTime DisplayTime, XrSpace TrackingSpace)
|
|
{
|
|
CurrentDisplayTime = DisplayTime;
|
|
CurrentBaseSpace = TrackingSpace;
|
|
|
|
if (bSupportMRCExtension && IsInGameThread())
|
|
{
|
|
if (bIsMRCRunning && !RCSceneCapture2DPICO)
|
|
{
|
|
if (!World && GEngine)
|
|
{
|
|
const TIndirectArray<FWorldContext>& WorldContexts = GEngine->GetWorldContexts();
|
|
for (const FWorldContext& Context : WorldContexts)
|
|
{
|
|
if (((Context.WorldType == EWorldType::PIE) || (Context.WorldType == EWorldType::Game)) && (Context.World() != NULL))
|
|
{
|
|
World = Context.World();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (World)
|
|
{
|
|
FActorSpawnParameters SpawnInfo;
|
|
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
SpawnInfo.bNoFail = true;
|
|
SpawnInfo.ObjectFlags = RF_Transient;
|
|
RCSceneCapture2DPICO = World->SpawnActor<AMRCCameraPICO>(SpawnInfo);
|
|
RCSceneCapture2DPICO->SetActorEnableCollision(false);
|
|
}
|
|
}
|
|
else if (!bIsMRCRunning && RCSceneCapture2DPICO)
|
|
{
|
|
RCSceneCapture2DPICO->Destroy();
|
|
RCSceneCapture2DPICO = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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.
|
|
|
|
if (bSupportMRCExtension && bIsMRCRunning &&!MRCLayer.IsValid())
|
|
{
|
|
if (Bridge && MRCLayerDesc.Texture.IsValid() && MRCLayerDesc.LeftTexture.IsValid())
|
|
{
|
|
MRCLayer = MakeShareable(new FOpenXRLayer(MRCLayerDesc));
|
|
|
|
{
|
|
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;
|
|
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);
|
|
MRCQuadLayerRight = MRCQuadLayer;
|
|
}
|
|
|
|
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);
|
|
MRCQuadLayerLeft = MRCQuadLayer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MRCLayer.Reset();
|
|
}
|
|
}
|
|
else if(MRCLayer.IsValid() && (!MRCLayerDesc.Texture.IsValid() || !MRCLayerDesc.LeftTexture.IsValid()))
|
|
{
|
|
MRCLayer.Reset();
|
|
}
|
|
|
|
if (MRCLayer.IsValid() && MRCLayer->RightEye.Swapchain.IsValid() && MRCLayer->LeftEye.Swapchain.IsValid())
|
|
{
|
|
//Right SwapChain
|
|
const FXRSwapChainPtr& RightDstSwapChain = MRCLayer->RightEye.Swapchain;
|
|
RHICmdList.EnqueueLambda([RightDstSwapChain](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
RightDstSwapChain->IncrementSwapChainIndex_RHIThread();
|
|
RightDstSwapChain->WaitCurrentImage_RHIThread(OPENXR_SWAPCHAIN_WAIT_TIMEOUT);
|
|
});
|
|
|
|
FRHITexture* RightSrcTexture = MRCLayer->Desc.Texture->GetTexture2D();
|
|
const FIntRect RightDstRect(FIntPoint(0, 0), MRCLayer->RightEye.SwapchainSize.IntPoint());
|
|
FRHITexture* const RightDstTexture = MRCLayer->RightEye.Swapchain->GetTexture2DArray() ? MRCLayer->RightEye.Swapchain->GetTexture2DArray() : MRCLayer->RightEye.Swapchain->GetTexture2D();
|
|
CopyTexture_RenderThread(RHICmdList, RightDstTexture, RightSrcTexture, RightDstRect, FIntRect(), true, true, true, true);
|
|
|
|
RHICmdList.EnqueueLambda([RightDstSwapChain](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
RightDstSwapChain->ReleaseCurrentImage_RHIThread(nullptr);
|
|
});
|
|
|
|
//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*/, true);
|
|
|
|
RHICmdList.EnqueueLambda([LeftDstSwapChain](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
LeftDstSwapChain->ReleaseCurrentImage_RHIThread(nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
void FHMDPICO::UpdateCompositionLayers(XrSession InSession, TArray<const XrCompositionLayerBaseHeader*>& Headers)
|
|
{
|
|
if (bSupportMRCExtension && MRCLayer.IsValid())
|
|
{
|
|
Headers.Add(reinterpret_cast<const XrCompositionLayerBaseHeader*>(&MRCQuadLayerLeft));
|
|
Headers.Add(reinterpret_cast<const XrCompositionLayerBaseHeader*>(&MRCQuadLayerRight));
|
|
}
|
|
}
|
|
|
|
void FHMDPICO::UpdateCompositionLayers(XrSession InSession, TArray<XrCompositionLayerBaseHeader*>& Headers)
|
|
{
|
|
#ifndef PICO_CUSTOM_ENGINE
|
|
//Stereo Layer default support depth
|
|
for (int32 i = 0; i < Headers.Num(); i++)
|
|
{
|
|
if (OpenXRHMD && OpenXRHMD->GetTrackedDeviceSpace(IXRTrackingSystem::HMDDeviceId) == Headers[i]->space)
|
|
{
|
|
continue;//skip face-lock layer
|
|
}
|
|
|
|
if (Headers[i]->type == XR_TYPE_COMPOSITION_LAYER_QUAD
|
|
|| Headers[i]->type == XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR
|
|
|| Headers[i]->type == XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR)
|
|
{
|
|
BlendState.next = const_cast<void*>(Headers[i]->next);
|
|
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;
|
|
Headers[i]->next = &BlendState;
|
|
}
|
|
}
|
|
#endif // PICO_CUSTOM_ENGINE
|
|
|
|
if (bSupportMRCExtension && MRCLayer.IsValid())
|
|
{
|
|
Headers.Add(reinterpret_cast<XrCompositionLayerBaseHeader*>(&MRCQuadLayerLeft));
|
|
Headers.Add(reinterpret_cast<XrCompositionLayerBaseHeader*>(&MRCQuadLayerRight));
|
|
}
|
|
}
|
|
|
|
bool FHMDPICO::GetSupportedDisplayRefreshRates(TArray<float>& Rates)
|
|
{
|
|
Rates = SupportedDisplayRefreshRates;
|
|
return bSupportDisplayRefreshRate;
|
|
}
|
|
|
|
bool FHMDPICO::GetCurrentDisplayRefreshRate(float& Rate)
|
|
{
|
|
Rate = CurrentDisplayRefreshRate;
|
|
return bSupportDisplayRefreshRate;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void FHMDPICO::CopyTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture* DstTexture, FRHITexture* SrcTexture, FIntRect DstRect, FIntRect SrcRect, bool bAlphaPremultiply, bool bNoAlpha, bool bClearGreen, bool bInvertY, bool bInvertAlpha) const
|
|
{
|
|
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;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
bool FHMDPICO::IsEyeTrackerSupported(bool& Supported)
|
|
{
|
|
Supported = false;
|
|
if (Instance != XR_NULL_HANDLE && System != XR_NULL_SYSTEM_ID)
|
|
{
|
|
if (IOpenXRHMDModule::Get().IsExtensionEnabled(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME))
|
|
{
|
|
XrSystemEyeGazeInteractionPropertiesEXT EyeGazeInteractionProperties = { XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT };
|
|
XrSystemProperties systemProperties = { XR_TYPE_SYSTEM_PROPERTIES, &EyeGazeInteractionProperties };
|
|
XR_ENSURE(xrGetSystemProperties(Instance, System, &systemProperties));
|
|
Supported = EyeGazeInteractionProperties.supportsEyeGazeInteraction == XR_TRUE;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FHMDPICO::CreateMRCLayer(class UTexture* BackgroundRTTexture, class UTexture* ForegroundRTTexture)
|
|
{
|
|
if (BackgroundRTTexture && BackgroundRTTexture->GetResource() && ForegroundRTTexture && ForegroundRTTexture->GetResource())
|
|
{
|
|
MRCLayerDesc.PositionType = IStereoLayers::ELayerType::FaceLocked;
|
|
MRCLayerDesc.Transform.SetLocation(FVector(100, 0, 0));
|
|
MRCLayerDesc.Texture = ForegroundRTTexture->GetResource()->TextureRHI;
|
|
MRCLayerDesc.LeftTexture = BackgroundRTTexture->GetResource()->TextureRHI;
|
|
MRCLayerDesc.Flags = IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_CONTINUOUS_UPDATE | IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL;
|
|
MRCLayerDesc.QuadSize = FVector2D(100, 100);
|
|
}
|
|
}
|
|
|
|
void FHMDPICO::DestroyMRCLayer()
|
|
{
|
|
MRCLayerDesc.Texture = nullptr;
|
|
MRCLayerDesc.LeftTexture = nullptr;
|
|
MRCLayer.Reset();
|
|
}
|
|
|
|
bool FHMDPICO::GetExternalCameraInfo(int32& width, int32& height, float& fov)
|
|
{
|
|
if (MRCDebugMode.UseCustomCameraInfo)
|
|
{
|
|
width = MRCDebugMode.Width;
|
|
height = MRCDebugMode.Height;
|
|
fov = MRCDebugMode.Fov;
|
|
return true;
|
|
}
|
|
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;
|
|
World = WorldContext;
|
|
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 = {};
|
|
}
|
|
}
|
|
|
|
bool FHMDPICO::NeedReallocateTexture(bool bLeft, const FOpenXRLayer& Layer)
|
|
{
|
|
if (bLeft)
|
|
{
|
|
if (!Layer.Desc.LeftTexture.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FRHITexture* Texture = Layer.Desc.LeftTexture->GetTexture2D();
|
|
if (!Texture)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!Layer.LeftEye.Swapchain.IsValid())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return Layer.LeftEye.SwapchainSize != Texture->GetSizeXY();
|
|
}
|
|
else
|
|
{
|
|
if (!Layer.Desc.Texture.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FRHITexture* Texture = Layer.Desc.Texture->GetTexture2D();
|
|
if (!Texture)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!Layer.RightEye.Swapchain.IsValid())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return Layer.RightEye.SwapchainSize != Texture->GetSizeXY();
|
|
}
|
|
}
|
|
|
|
void FHMDPICO::UpdateLayerSwapchainTexture(int64 WaitTimeOut, const FOpenXRLayer& Layer, FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
const bool bNoAlpha = Layer.Desc.Flags & IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL;
|
|
|
|
// We need to copy each layer into an OpenXR swapchain so they can be displayed by the compositor.
|
|
if (Layer.RightEye.Swapchain.IsValid() && Layer.Desc.Texture.IsValid())
|
|
{
|
|
if (Layer.RightEye.bUpdateTexture)
|
|
{
|
|
const FXRSwapChainPtr& DstSwapChain = Layer.RightEye.Swapchain;
|
|
RHICmdList.EnqueueLambda([WaitTimeOut, DstSwapChain](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
DstSwapChain->IncrementSwapChainIndex_RHIThread();
|
|
DstSwapChain->WaitCurrentImage_RHIThread(WaitTimeOut);
|
|
});
|
|
|
|
FRHITexture* SrcTexture = Layer.Desc.Texture->GetTexture2D();
|
|
FIntRect DstRect(FIntPoint(0, 0), Layer.RightEye.SwapchainSize.IntPoint());
|
|
OpenXRHMD->CopyTexture_RenderThread(RHICmdList, SrcTexture, FIntRect(), Layer.RightEye.Swapchain->GetTexture2D(), DstRect, false, bNoAlpha);
|
|
|
|
// Enqueue a command to release the image after the copy is done
|
|
RHICmdList.EnqueueLambda([DstSwapChain](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
DstSwapChain->ReleaseCurrentImage_RHIThread(nullptr);
|
|
});
|
|
}
|
|
}
|
|
if (Layer.LeftEye.Swapchain.IsValid() && Layer.Desc.LeftTexture.IsValid())
|
|
{
|
|
if (Layer.LeftEye.bUpdateTexture)
|
|
{
|
|
const FXRSwapChainPtr& DstSwapChain = Layer.LeftEye.Swapchain;
|
|
RHICmdList.EnqueueLambda([WaitTimeOut, DstSwapChain](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
DstSwapChain->IncrementSwapChainIndex_RHIThread();
|
|
DstSwapChain->WaitCurrentImage_RHIThread(WaitTimeOut);
|
|
});
|
|
|
|
FRHITexture* SrcTexture = Layer.Desc.LeftTexture->GetTexture2D();
|
|
FIntRect DstRect(FIntPoint(0, 0), Layer.LeftEye.SwapchainSize.IntPoint());
|
|
OpenXRHMD->CopyTexture_RenderThread(RHICmdList, SrcTexture, FIntRect(), Layer.LeftEye.Swapchain->GetTexture2D(), DstRect, false, bNoAlpha);
|
|
|
|
// Enqueue a command to release the image after the copy is done
|
|
RHICmdList.EnqueueLambda([DstSwapChain](FRHICommandListImmediate& InRHICmdList)
|
|
{
|
|
DstSwapChain->ReleaseCurrentImage_RHIThread(nullptr);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
uint32 FHMDPICO::CreateLayer(const IStereoLayers::FLayerDesc& InLayerDesc)
|
|
{
|
|
FScopeLock LockLayers(&LayerCritSect);
|
|
uint32 LayerId = ++StereoLayerID;
|
|
check(LayerId != IStereoLayers::FLayerDesc::INVALID_LAYER_ID);
|
|
FOpenXRLayer& NewLayer = StereoLayersPICO.Emplace(LayerId, FOpenXRLayer(InLayerDesc));
|
|
NewLayer.SetLayerId(LayerId);
|
|
return LayerId;
|
|
}
|
|
|
|
void FHMDPICO::SetLayerDesc(uint32 ID, const IStereoLayers::FLayerDesc& Desc)
|
|
{
|
|
FScopeLock LockLayers(&LayerCritSect);
|
|
FOpenXRLayer* LayerFound = StereoLayersPICO.Find(ID);
|
|
if (LayerFound)
|
|
{
|
|
LayerFound->Desc = Desc;
|
|
}
|
|
}
|
|
|
|
void FHMDPICO::DestroyLayer(uint32 ID)
|
|
{
|
|
FScopeLock LockLayers(&LayerCritSect);
|
|
StereoLayersPICO.Remove(ID);
|
|
}
|
|
|
|
void FHMDPICO::MarkTextureForUpdate(uint32 ID)
|
|
{
|
|
FScopeLock LockLayers(&LayerCritSect);
|
|
FOpenXRLayer* LayerFound = StereoLayersPICO.Find(ID);
|
|
if (LayerFound)
|
|
{
|
|
// If the swapchain is static we need to re-allocate it before it can be updated
|
|
if (!(LayerFound->Desc.Flags & IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE))
|
|
{
|
|
LayerFound->RightEye.Swapchain.Reset();
|
|
LayerFound->LeftEye.Swapchain.Reset();
|
|
}
|
|
LayerFound->RightEye.bUpdateTexture = true;
|
|
LayerFound->LeftEye.bUpdateTexture = true;
|
|
}
|
|
}
|