Files
UnrealEngine/Engine/Plugins/Runtime/OpenXR/Source/OpenXRHMD/Private/FBFoveationImageGenerator.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

184 lines
7.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "FBFoveationImageGenerator.h"
#include "SceneView.h"
#include "OpenXRHMD.h"
#include "OpenXRHMD_Swapchain.h"
#include "RenderGraphBuilder.h"
static TAutoConsoleVariable<int32> CVarOpenXRFBFoveationLevel(
TEXT("xr.OpenXRFBFoveationLevel"),
0,
TEXT("Possible foveation levels as specified by the XrFoveationLevelFB enumeration.\n")
TEXT("0 = None, 1 = Low , 2 = Medium, 3 = High.\n"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static TAutoConsoleVariable<bool> CVarOpenXRFBFoveationDynamic(
TEXT("xr.OpenXRFBFoveationDynamic"),
false,
TEXT("Whether dynamically changing foveation based on performance headroom is enabled.\n"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static TAutoConsoleVariable<float> CVarOpenXRFBFoveationVerticalOffset(
TEXT("xr.OpenXRFBFoveationVerticalOffset"),
0,
TEXT("Desired vertical offset in degrees for the center of the foveation pattern.\n"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static TAutoConsoleVariable<int32> CVarFBFoveationPreview(
TEXT("xr.OpenXRFBFoveation.Preview"),
1,
TEXT("Whether to include FB foveation in VRS preview overlay. This is currently not implemented.")
TEXT("0 - off, 1 - on (default)"),
ECVF_RenderThreadSafe);
FFBFoveationImageGenerator::FFBFoveationImageGenerator(bool bIsFoveationExtensionSupported, XrInstance InInstance, FOpenXRHMD* HMD, bool bMobileMultiViewEnabled)
: bIsMobileMultiViewEnabled(bMobileMultiViewEnabled)
, CurrentFrameSwapchainIndex(0)
, bFoveationExtensionSupported(bIsFoveationExtensionSupported)
{
if (bFoveationExtensionSupported)
{
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrCreateFoveationProfileFB", (PFN_xrVoidFunction*)&xrCreateFoveationProfileFB));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrUpdateSwapchainFB", (PFN_xrVoidFunction*)&xrUpdateSwapchainFB));
XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrDestroyFoveationProfileFB", (PFN_xrVoidFunction*)&xrDestroyFoveationProfileFB));
int32 SanitisedFoveationLevel = FMath::Clamp(CVarOpenXRFBFoveationLevel->GetInt(), XrFoveationLevelFB::XR_FOVEATION_LEVEL_NONE_FB, XrFoveationLevelFB::XR_FOVEATION_LEVEL_HIGH_FB);
bool bFoveationDynamic = CVarOpenXRFBFoveationDynamic->GetBool();
FoveationLevel = static_cast<XrFoveationLevelFB>(SanitisedFoveationLevel);
// Vertical offset is in degrees so it does not need to be clamped to values above 0.
VerticalOffset = CVarOpenXRFBFoveationVerticalOffset->GetFloat();
FoveationDynamic = bFoveationDynamic ? XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB : XR_FOVEATION_DYNAMIC_DISABLED_FB;
}
OpenXRHMD = HMD;
}
FRDGTextureRef FFBFoveationImageGenerator::GetImage(FRDGBuilder& GraphBuilder, const FViewInfo& ViewInfo, FVariableRateShadingImageManager::EVRSImageType ImageType, bool bGetSoftwareImage)
{
if (!bFoveationExtensionSupported || !OpenXRHMD || FoveationImages.IsEmpty() || bGetSoftwareImage)
{
return nullptr;
}
if (FoveationImages.IsValidIndex(CurrentFrameSwapchainIndex))
{
FTextureRHIRef SwapchainTexture = FoveationImages[CurrentFrameSwapchainIndex];
TRefCountPtr<IPooledRenderTarget> PooledRenderTarget = CreateRenderTarget(SwapchainTexture, *SwapchainTexture->GetName().ToString());
return GraphBuilder.RegisterExternalTexture(PooledRenderTarget, *SwapchainTexture->GetName().ToString(), ERDGTextureFlags::SkipTracking);
}
else
{
UE_LOG(LogHMD, Error, TEXT("No valid color swapchain to get foveation images from."));
return nullptr;
}
}
void FFBFoveationImageGenerator::PrepareImages(FRDGBuilder& GraphBuilder, const FSceneViewFamily& ViewFamily, const FMinimalSceneTextures& SceneTextures, bool bPrepareHardwareImages, bool bPrepareSoftwareImages)
{
//Not implemented as images are updated in UpdateFoveationImages only
//when foveation parameters change or when the color swapchain is reallocated.
return;
}
bool FFBFoveationImageGenerator::IsEnabled() const
{
if (OpenXRHMD)
{
return OpenXRHMD->IsStereoEnabled() && bFoveationExtensionSupported;
}
return false;
}
bool FFBFoveationImageGenerator::IsSupportedByView(const FSceneView& View) const
{
// Only used for XR views
return IStereoRendering::IsStereoEyeView(View);
}
// This is currently not implemented.
FRDGTextureRef FFBFoveationImageGenerator::GetDebugImage(FRDGBuilder& GraphBuilder, const FViewInfo& ViewInfo, FVariableRateShadingImageManager::EVRSImageType ImageType, bool bGetSoftwareImage)
{
return nullptr;
}
void FFBFoveationImageGenerator::UpdateFoveationImages(bool bReallocatedSwapchain)
{
if (!OpenXRHMD)
{
return;
}
int32 SanitisedFoveationLevel = FMath::Clamp(CVarOpenXRFBFoveationLevel->GetInt(), XrFoveationLevelFB::XR_FOVEATION_LEVEL_NONE_FB, XrFoveationLevelFB::XR_FOVEATION_LEVEL_HIGH_FB);
bool bFoveationDynamic = CVarOpenXRFBFoveationDynamic->GetBool();
float SanitisedVerticalOffset = CVarOpenXRFBFoveationVerticalOffset->GetFloat();
//Starting foveation level value outside actual range to force image creation at first update.
static int32 LastSanitisedFoveationLevel = -1;
static bool bLastFoveationDynamic = false;
static float LastSanitisedVerticalOffset = 0.0f;
bool bUpdateFoveationImages = false;
if (LastSanitisedFoveationLevel != SanitisedFoveationLevel)
{
LastSanitisedFoveationLevel = SanitisedFoveationLevel;
bUpdateFoveationImages = true;
}
if (bLastFoveationDynamic != bFoveationDynamic)
{
bLastFoveationDynamic = bFoveationDynamic;
bUpdateFoveationImages = true;
}
if (LastSanitisedVerticalOffset != SanitisedVerticalOffset)
{
LastSanitisedVerticalOffset = SanitisedVerticalOffset;
bUpdateFoveationImages = true;
}
// If the swapchain has been reallocated we need to update the foveation images even if the foveation params haven't changed.
bUpdateFoveationImages |= bReallocatedSwapchain;
if (!bUpdateFoveationImages)
{
return;
}
FoveationLevel = static_cast<XrFoveationLevelFB>(SanitisedFoveationLevel);
VerticalOffset = SanitisedVerticalOffset;
FoveationDynamic = bFoveationDynamic ? XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB : XR_FOVEATION_DYNAMIC_DISABLED_FB;
XrFoveationLevelProfileCreateInfoFB FoveationLevelProfileInfo{ XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB };
FoveationLevelProfileInfo.next = nullptr;
FoveationLevelProfileInfo.level = FoveationLevel;
FoveationLevelProfileInfo.verticalOffset = VerticalOffset;
FoveationLevelProfileInfo.dynamic = FoveationDynamic;
XrFoveationProfileCreateInfoFB FoveationCreateInfo{ XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB };
FoveationCreateInfo.next = &FoveationLevelProfileInfo;
XrFoveationProfileFB FoveationProfile = XR_NULL_HANDLE;
XrSession Session = OpenXRHMD->GetSession();
FOpenXRSwapchain* ColorSwapchain = OpenXRHMD->GetColorSwapchain_RenderThread();
if (Session && ColorSwapchain != nullptr)
{
XR_ENSURE(xrCreateFoveationProfileFB(Session, &FoveationCreateInfo, &FoveationProfile));
XrSwapchainStateFoveationFB SwapchainFoveationState{ XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB };
SwapchainFoveationState.flags = 0; // As per OpenXR specification.
SwapchainFoveationState.next = nullptr;
SwapchainFoveationState.profile = FoveationProfile;
XR_ENSURE(xrUpdateSwapchainFB(ColorSwapchain->GetHandle(), reinterpret_cast<const XrSwapchainStateBaseHeaderFB*>(&SwapchainFoveationState)));
XR_ENSURE(xrDestroyFoveationProfileFB(FoveationProfile));
ColorSwapchain->GetFragmentDensityMaps(FoveationImages, bIsMobileMultiViewEnabled);
}
}
void FFBFoveationImageGenerator::SetCurrentFrameSwapchainIndex(int32 InCurrentFrameSwapchainIndex)
{
CurrentFrameSwapchainIndex = InCurrentFrameSwapchainIndex;
}