582 lines
19 KiB
C++
582 lines
19 KiB
C++
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
|
|
#include "OculusXRPassthroughXR.h"
|
|
|
|
#include "Engine/GameEngine.h"
|
|
#include "Engine/RendererSettings.h"
|
|
#include "IOpenXRHMDModule.h"
|
|
#include "Materials/Material.h"
|
|
#include "OculusXRHMD/Private/OculusXRResourceHolder.h"
|
|
#include "OculusXRHMD_CustomPresent.h"
|
|
#include "OculusXRHMDRuntimeSettings.h"
|
|
#include "OculusXRPassthroughXRFunctions.h"
|
|
#include "OculusXRPassthroughModule.h"
|
|
#include "OculusXRPassthroughEventHandling.h"
|
|
#include "OpenXRHMD.h"
|
|
#include "OpenXRHMD_Swapchain.h"
|
|
#include "StereoRendering.h"
|
|
#include "RenderGraphBuilder.h"
|
|
|
|
#include "XRThreadUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "OculusXRPassthrough"
|
|
|
|
namespace XRPassthrough
|
|
{
|
|
TWeakPtr<FPassthroughXR> FPassthroughXR::GetInstance()
|
|
{
|
|
return FOculusXRPassthroughModule::Get().GetPassthroughExtensionPlugin();
|
|
}
|
|
|
|
FPassthroughXR::FPassthroughXR()
|
|
: InvAlphaTexture(nullptr)
|
|
, ColorSwapchain(nullptr)
|
|
, ColorSwapChainTexture(nullptr)
|
|
, ProjectionLayerAlphaBlend{}
|
|
, Layers_RenderThread{}
|
|
, bPassthroughInitialized(false)
|
|
, PassthroughInstance{ XR_NULL_HANDLE }
|
|
, Settings(nullptr)
|
|
, OpenXRHMD(nullptr)
|
|
, WorldToMetersScale(100.0f)
|
|
, WorldToMetersScale_RenderThread(100.0f)
|
|
{
|
|
static const FName RendererModuleName("Renderer");
|
|
RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName);
|
|
Settings = MakeShareable(new FSettings());
|
|
}
|
|
|
|
FPassthroughXR::~FPassthroughXR()
|
|
{
|
|
}
|
|
|
|
void FPassthroughXR::RegisterAsOpenXRExtension()
|
|
{
|
|
#if defined(WITH_OCULUS_BRANCH)
|
|
// Feature not enabled on Marketplace build. Currently only for the meta fork
|
|
RegisterOpenXRExtensionModularFeature();
|
|
#endif
|
|
}
|
|
|
|
bool FPassthroughXR::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
|
|
{
|
|
OutExtensions.Add(XR_FB_PASSTHROUGH_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_META_PASSTHROUGH_LAYER_RESUMED_EVENT_EXTENSION_NAME);
|
|
return true;
|
|
}
|
|
|
|
bool FPassthroughXR::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
|
|
{
|
|
OutExtensions.Add(XR_FB_COMPOSITION_LAYER_ALPHA_BLEND_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_FB_TRIANGLE_MESH_EXTENSION_NAME);
|
|
OutExtensions.Add(XR_META_PASSTHROUGH_COLOR_LUT_EXTENSION_NAME);
|
|
return true;
|
|
}
|
|
|
|
const void* FPassthroughXR::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext)
|
|
{
|
|
if (InModule != nullptr)
|
|
{
|
|
Settings->bExtLayerAlphaBlendAvailable = InModule->IsExtensionEnabled(XR_FB_COMPOSITION_LAYER_ALPHA_BLEND_EXTENSION_NAME);
|
|
Settings->bExtPassthroughAvailable = InModule->IsExtensionEnabled(XR_FB_PASSTHROUGH_EXTENSION_NAME);
|
|
Settings->bExtTriangleMeshAvailable = InModule->IsExtensionEnabled(XR_FB_TRIANGLE_MESH_EXTENSION_NAME);
|
|
Settings->bExtColorLutAvailable = InModule->IsExtensionEnabled(XR_META_PASSTHROUGH_COLOR_LUT_EXTENSION_NAME);
|
|
Settings->bExtLayerResumedEventAvailable = InModule->IsExtensionEnabled(XR_META_PASSTHROUGH_LAYER_RESUMED_EVENT_EXTENSION_NAME);
|
|
}
|
|
return InNext;
|
|
}
|
|
|
|
const void* FPassthroughXR::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext)
|
|
{
|
|
InitOpenXRFunctions(InInstance);
|
|
|
|
Settings->PokeAHoleMaterial = Cast<UMaterial>(FSoftObjectPath(TEXT("/OculusXR/Materials/PokeAHoleMaterial")).TryLoad());
|
|
|
|
OpenXRHMD = (FOpenXRHMD*)GEngine->XRSystem.Get();
|
|
|
|
const UOculusXRHMDRuntimeSettings* HMDSettings = GetDefault<UOculusXRHMDRuntimeSettings>();
|
|
Settings->bPassthroughEnabled = HMDSettings->bInsightPassthroughEnabled;
|
|
|
|
return InNext;
|
|
}
|
|
|
|
void FPassthroughXR::PostCreateSession(XrSession InSession)
|
|
{
|
|
if (Settings->bPassthroughEnabled)
|
|
{
|
|
ExecuteOnRenderThread([this, InSession](FRHICommandListImmediate& RHICmdList) {
|
|
InitializePassthrough(InSession);
|
|
});
|
|
}
|
|
}
|
|
|
|
void FPassthroughXR::OnDestroySession(XrSession InSession)
|
|
{
|
|
// Release resources
|
|
ExecuteOnRenderThread([this, InSession]() {
|
|
Layers_RenderThread.Reset();
|
|
InvAlphaTexture.SafeRelease();
|
|
|
|
DeferredDeletion.HandleLayerDeferredDeletionQueue_RenderThread(true);
|
|
|
|
ShutdownPassthrough(InSession);
|
|
});
|
|
|
|
OpenXRHMD = nullptr;
|
|
}
|
|
|
|
void* FPassthroughXR::OnWaitFrame(XrSession InSession, void* InNext)
|
|
{
|
|
Update_GameThread(InSession);
|
|
return InNext;
|
|
}
|
|
|
|
bool FPassthroughXR::IsPassthroughEnabled(void) const
|
|
{
|
|
return bPassthroughInitialized;
|
|
}
|
|
|
|
void FPassthroughXR::InvertTextureAlpha_RenderThread(FRHICommandList& RHICmdList, FRHITexture* Texture, FRHITexture* TempTexture, const FIntRect& ViewportRect)
|
|
{
|
|
{
|
|
FRHITexture* SrcTexture = Texture;
|
|
FRHITexture* DstTexture = TempTexture;
|
|
const FIntRect SrcRect(ViewportRect);
|
|
const FIntRect DstRect(0, 0, ViewportRect.Size().X, ViewportRect.Size().Y);
|
|
|
|
const bool bAlphaPremultiply = false;
|
|
const bool bNoAlphaWrite = false;
|
|
const bool bInvertSrcY = false;
|
|
const bool sRGBSource = false;
|
|
const bool bInvertAlpha = true;
|
|
const auto FeatureLevel = GEngine ? GEngine->GetDefaultWorldFeatureLevel() : GMaxRHIFeatureLevel;
|
|
const bool bUsingVulkan = RHIGetInterfaceType() == ERHIInterfaceType::Vulkan;
|
|
OculusXRHMD::FCustomPresent::CopyTexture_RenderThread(RHICmdList.GetAsImmediate(), RendererModule, DstTexture, SrcTexture, FeatureLevel, bUsingVulkan,
|
|
DstRect, SrcRect, bAlphaPremultiply, bNoAlphaWrite, bInvertSrcY, sRGBSource, bInvertAlpha);
|
|
}
|
|
|
|
{
|
|
FRHICopyTextureInfo CopyInfo;
|
|
CopyInfo.Size = FIntVector(ViewportRect.Size().X, ViewportRect.Size().Y, 1);
|
|
CopyInfo.SourcePosition = FIntVector::ZeroValue;
|
|
CopyInfo.DestPosition = FIntVector(ViewportRect.Min.X, ViewportRect.Min.Y, 0);
|
|
CopyInfo.SourceSliceIndex = 0;
|
|
CopyInfo.DestSliceIndex = 0;
|
|
|
|
if (Texture->GetDesc().IsTextureArray() && TempTexture->GetDesc().IsTextureArray())
|
|
{
|
|
CopyInfo.NumSlices = FMath::Min(Texture->GetDesc().ArraySize, TempTexture->GetDesc().ArraySize);
|
|
}
|
|
|
|
FRHITexture* SrcTexture = TempTexture;
|
|
FRHITexture* DstTexture = Texture;
|
|
RHICmdList.Transition(FRHITransitionInfo(SrcTexture, ERHIAccess::Unknown, ERHIAccess::CopySrc));
|
|
RHICmdList.Transition(FRHITransitionInfo(DstTexture, ERHIAccess::Unknown, ERHIAccess::CopyDest));
|
|
RHICmdList.CopyTexture(SrcTexture, DstTexture, CopyInfo);
|
|
RHICmdList.Transition(FRHITransitionInfo(DstTexture, ERHIAccess::CopyDest, ERHIAccess::SRVMask));
|
|
RHICmdList.Transition(FRHITransitionInfo(SrcTexture, ERHIAccess::CopySrc, ERHIAccess::SRVMask));
|
|
}
|
|
}
|
|
|
|
FPassthroughLayerPtr FPassthroughXR::CreateStereoLayerFromDesc(const IStereoLayers::FLayerDesc& LayerDesc) const
|
|
{
|
|
FPassthroughLayerPtr Layer = nullptr;
|
|
|
|
if (FPassthroughLayer::IsPassthoughLayerDesc(LayerDesc))
|
|
{
|
|
check(PassthroughInstance != XR_NULL_HANDLE);
|
|
Layer = MakeShareable(new FPassthroughLayer(PassthroughInstance, GetInstance()));
|
|
}
|
|
return Layer;
|
|
}
|
|
|
|
void FPassthroughXR::OnSetupLayers_RenderThread(XrSession InSession, const TArray<uint32_t>& LayerIds)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
#ifdef WITH_OCULUS_BRANCH
|
|
ColorSwapchain = OpenXRHMD->GetColorSwapchain_RenderThread();
|
|
#endif
|
|
|
|
#if PLATFORM_WINDOWS
|
|
if (ColorSwapchain && InvAlphaTexture == nullptr)
|
|
{
|
|
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
|
const auto CVarPropagateAlpha = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PostProcessing.PropagateAlpha"));
|
|
const bool bPropagateAlpha = EAlphaChannelMode::FromInt(CVarPropagateAlpha->GetValueOnRenderThread()) == EAlphaChannelMode::AllowThroughTonemapper;
|
|
#else
|
|
const auto CVarPropagateAlpha = IConsoleManager::Get().FindConsoleVariable(TEXT("r.PostProcessing.PropagateAlpha"));
|
|
const bool bPropagateAlpha = CVarPropagateAlpha->GetBool();
|
|
#endif
|
|
if (bPropagateAlpha)
|
|
{
|
|
const FRHITextureDesc TextureDesc = ColorSwapchain->GetTexture()->GetDesc();
|
|
uint32 SizeX = TextureDesc.GetSize().X;
|
|
uint32 SizeY = TextureDesc.GetSize().Y;
|
|
EPixelFormat ColorFormat = TextureDesc.Format;
|
|
uint32 NumMips = TextureDesc.NumMips;
|
|
uint32 NumSamples = TextureDesc.NumSamples;
|
|
|
|
FClearValueBinding ColorTextureBinding = FClearValueBinding::Black;
|
|
|
|
const ETextureCreateFlags InvTextureCreateFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable;
|
|
FRHITextureCreateDesc InvTextureDesc{};
|
|
if (ColorSwapchain->GetTexture2DArray() != nullptr)
|
|
{
|
|
InvTextureDesc = FRHITextureCreateDesc::Create2DArray(TEXT("InvAlphaTexture"))
|
|
.SetArraySize(2)
|
|
.SetExtent(SizeX, SizeY)
|
|
.SetFormat(ColorFormat)
|
|
.SetNumMips(NumMips)
|
|
.SetNumSamples(NumSamples)
|
|
.SetFlags(InvTextureCreateFlags | TexCreate_TargetArraySlicesIndependently)
|
|
.SetClearValue(ColorTextureBinding);
|
|
}
|
|
else
|
|
{
|
|
InvTextureDesc = FRHITextureCreateDesc::Create2D(TEXT("InvAlphaTexture"))
|
|
.SetExtent(SizeX, SizeY)
|
|
.SetFormat(ColorFormat)
|
|
.SetNumMips(NumMips)
|
|
.SetNumSamples(NumSamples)
|
|
.SetFlags(InvTextureCreateFlags)
|
|
.SetClearValue(ColorTextureBinding);
|
|
}
|
|
InvAlphaTexture = RHICreateTexture(InvTextureDesc);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FPassthroughXR::UpdateCompositionLayers(XrSession InSession, TArray<XrCompositionLayerBaseHeaderType*>& Headers)
|
|
{
|
|
check(IsInRenderingThread() || IsInRHIThread());
|
|
|
|
TArray<FPassthroughLayerPtr> SortedLayers = Layers_RenderThread;
|
|
SortedLayers.Sort(FLayerDesc_ComparePriority());
|
|
|
|
// Headers array already contains (at least) one layer which is the eye's layer.
|
|
// Underlay/SupportDetph layers need to be inserted before that layer, ordered by priority.
|
|
int EyeLayerId = 0;
|
|
for (const FPassthroughLayerPtr& Layer : SortedLayers)
|
|
{
|
|
if (Layer->IsBackgroundLayer() || Layer->PassthroughSupportsDepth())
|
|
{
|
|
XrCompositionLayerBaseHeaderType* CompositionLayerHeader = Layer->GetXrCompositionLayerHeader();
|
|
if (CompositionLayerHeader != nullptr)
|
|
{
|
|
Headers.Insert(CompositionLayerHeader, EyeLayerId++);
|
|
}
|
|
}
|
|
else if (Layer->IsOverlayLayer())
|
|
{
|
|
XrCompositionLayerBaseHeaderType* CompositionLayerHeader = Layer->GetXrCompositionLayerHeader();
|
|
if (CompositionLayerHeader != nullptr)
|
|
{
|
|
Headers.Add(CompositionLayerHeader);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_OCULUS_BRANCH
|
|
bool FPassthroughXR::OnEndGameFrame(FWorldContext& WorldContext)
|
|
{
|
|
FXRTrackingSystemBase* TS = static_cast<FXRTrackingSystemBase*>(GEngine->XRSystem.Get());
|
|
TrackingToWorld = TS->GetTrackingToWorldTransform();
|
|
WorldToMetersScale = TS->GetWorldToMetersScale();
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
void FPassthroughXR::OnBeginRendering_GameThread(XrSession InSession)
|
|
{
|
|
// Send game thread layers to render thread ones
|
|
TArray<FPassthroughLayerPtr> XLayers;
|
|
XLayers.Empty(LayerMap.Num());
|
|
|
|
for (auto& Pair : LayerMap)
|
|
{
|
|
XLayers.Emplace(Pair.Value->Clone());
|
|
}
|
|
|
|
XLayers.Sort(FPassthroughLayerPtr_CompareId());
|
|
|
|
ENQUEUE_RENDER_COMMAND(TransferFrameStateToRenderingThread)
|
|
([this, TrackingToWorld = TrackingToWorld, WorldToMetersScale = WorldToMetersScale, XLayers, InSession](FRHICommandListImmediate& RHICmdList) mutable {
|
|
TrackingToWorld_RenderThread = TrackingToWorld;
|
|
WorldToMetersScale_RenderThread = WorldToMetersScale;
|
|
|
|
int32 XLayerIndex = 0;
|
|
int32 LayerIndex_RenderThread = 0;
|
|
TArray<FPassthroughLayerPtr> ValidLayers;
|
|
|
|
// Scan for changes
|
|
while (XLayerIndex < XLayers.Num() && LayerIndex_RenderThread < Layers_RenderThread.Num())
|
|
{
|
|
uint32 LayerIdA = XLayers[XLayerIndex]->GetDesc().GetLayerId();
|
|
uint32 LayerIdB = Layers_RenderThread[LayerIndex_RenderThread]->GetDesc().GetLayerId();
|
|
|
|
if (LayerIdA < LayerIdB) // If a layer was inserted in the middle of existing ones
|
|
{
|
|
if (XLayers[XLayerIndex]->Initialize_RenderThread(InSession))
|
|
{
|
|
ValidLayers.Add(XLayers[XLayerIndex]);
|
|
}
|
|
XLayerIndex++;
|
|
}
|
|
else if (LayerIdA > LayerIdB) // If a layer was removed in the middle of existing ones
|
|
{
|
|
DeferredDeletion.AddOpenXRLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]);
|
|
}
|
|
else // This layer is not new nor removed
|
|
{
|
|
if (XLayers[XLayerIndex]->Initialize_RenderThread(InSession, Layers_RenderThread[LayerIndex_RenderThread].Get()))
|
|
{
|
|
LayerIndex_RenderThread++;
|
|
ValidLayers.Add(XLayers[XLayerIndex]);
|
|
}
|
|
XLayerIndex++;
|
|
}
|
|
}
|
|
|
|
// Create missing layers
|
|
while (XLayerIndex < XLayers.Num())
|
|
{
|
|
if (XLayers[XLayerIndex]->Initialize_RenderThread(InSession))
|
|
{
|
|
ValidLayers.Add(XLayers[XLayerIndex]);
|
|
}
|
|
XLayerIndex++;
|
|
}
|
|
|
|
// Delete remaining layers
|
|
while (LayerIndex_RenderThread < Layers_RenderThread.Num())
|
|
{
|
|
DeferredDeletion.AddOpenXRLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]);
|
|
}
|
|
|
|
Layers_RenderThread = ValidLayers;
|
|
|
|
DeferredDeletion.HandleLayerDeferredDeletionQueue_RenderThread();
|
|
});
|
|
}
|
|
|
|
#ifdef WITH_OCULUS_BRANCH
|
|
void FPassthroughXR::OnBeginRenderingLate_RenderThread(XrSession InSession, FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
ColorSwapchain = OpenXRHMD->GetColorSwapchain_RenderThread();
|
|
|
|
if (ColorSwapchain && InvAlphaTexture)
|
|
{
|
|
ColorSwapChainTexture = ColorSwapchain->GetTexture();
|
|
}
|
|
}
|
|
|
|
void FPassthroughXR::FinishRenderFrame_RenderThread(FRDGBuilder& GraphBuilder)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
if (ColorSwapChainTexture && InvAlphaTexture)
|
|
{
|
|
FRDGEventName PassName = RDG_EVENT_NAME("FPassthroughXR_InvertTextureAlpha");
|
|
GraphBuilder.AddPass(MoveTemp(PassName), ERDGPassFlags::None,
|
|
[this, SwapchainTexture = ColorSwapChainTexture](FRHICommandListImmediate& RHICmdList) {
|
|
const FRHITextureDesc TextureDesc = SwapchainTexture->GetDesc();
|
|
FIntRect TextureRect = FIntRect(0, 0, TextureDesc.GetSize().X, TextureDesc.GetSize().Y);
|
|
|
|
InvertTextureAlpha_RenderThread(RHICmdList, SwapchainTexture, InvAlphaTexture, TextureRect);
|
|
});
|
|
}
|
|
|
|
XrSpace Space = OpenXRHMD->GetTrackingSpace();
|
|
XrTime DisplayTime = OpenXRHMD->GetDisplayTime();
|
|
|
|
FRDGEventName PassName = RDG_EVENT_NAME("FPassthroughXR_UpdatePassthroughLayers");
|
|
GraphBuilder.AddPass(MoveTemp(PassName), ERDGPassFlags::None,
|
|
[this, Space, DisplayTime](FRHICommandListImmediate& RHICmdList) {
|
|
for (const FPassthroughLayerPtr& Layer : Layers_RenderThread)
|
|
{
|
|
Layer->UpdatePassthrough_RenderThread(RHICmdList,
|
|
Space,
|
|
DisplayTime,
|
|
WorldToMetersScale_RenderThread,
|
|
TrackingToWorld_RenderThread);
|
|
}
|
|
});
|
|
}
|
|
#endif
|
|
|
|
void FPassthroughXR::OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader)
|
|
{
|
|
switch (InHeader->type)
|
|
{
|
|
case XR_TYPE_EVENT_DATA_PASSTHROUGH_LAYER_RESUMED_META:
|
|
if (Settings->bExtLayerResumedEventAvailable)
|
|
{
|
|
const XrEventDataPassthroughLayerResumedMETA* LayerResumedEvent =
|
|
reinterpret_cast<const XrEventDataPassthroughLayerResumedMETA*>(InHeader);
|
|
|
|
for (auto& Pair : LayerMap)
|
|
{
|
|
if (Pair.Value->GetLayerHandle() == LayerResumedEvent->layer)
|
|
{
|
|
UE_LOG(LogOculusXRPassthrough, Log, TEXT("FOculusXRPassthroughEventHandling - Passthrough Layer #%d resumed"), Pair.Value->GetDesc().GetLayerId());
|
|
|
|
// Send event
|
|
OculusXRPassthrough::FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed.Broadcast(Pair.Value->GetDesc().GetLayerId());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
const void* FPassthroughXR::OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags)
|
|
{
|
|
check(IsInRenderingThread() || IsInRHIThread());
|
|
|
|
bool bHasBackgroundLayer = false;
|
|
for (const FPassthroughLayerPtr& Layer : Layers_RenderThread)
|
|
{
|
|
if (Layer->IsBackgroundLayer() || Layer->PassthroughSupportsDepth())
|
|
{
|
|
bHasBackgroundLayer = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHasBackgroundLayer)
|
|
{
|
|
OutFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
|
|
|
if (Settings->bExtLayerAlphaBlendAvailable)
|
|
{
|
|
InNext = &ProjectionLayerAlphaBlend;
|
|
ProjectionLayerAlphaBlend.type = XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB;
|
|
ProjectionLayerAlphaBlend.srcFactorColor = XR_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA_FB;
|
|
ProjectionLayerAlphaBlend.srcFactorAlpha = XR_BLEND_FACTOR_ONE_FB;
|
|
ProjectionLayerAlphaBlend.dstFactorColor = XR_BLEND_FACTOR_SRC_ALPHA_FB;
|
|
ProjectionLayerAlphaBlend.dstFactorAlpha = XR_BLEND_FACTOR_ZERO_FB;
|
|
ProjectionLayerAlphaBlend.next = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT is required for the eye layer to be correctly blended
|
|
// when XR_FB_composition_layer_alpha_blend extension is not available (e.g. Link)
|
|
OutFlags |= XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
|
|
}
|
|
}
|
|
|
|
return InNext;
|
|
}
|
|
|
|
#ifdef WITH_OCULUS_BRANCH
|
|
void FPassthroughXR::OnCreateLayer(const IStereoLayers::FLayerDesc& InLayerDesc, uint32 LayerId)
|
|
{
|
|
OculusXRHMD::CheckInGameThread();
|
|
|
|
if (FPassthroughLayerPtr Layer = CreateStereoLayerFromDesc(InLayerDesc))
|
|
{
|
|
Layer->SetDesc(InLayerDesc);
|
|
|
|
LayerMap.Add(LayerId, Layer);
|
|
}
|
|
}
|
|
|
|
void FPassthroughXR::OnDestroyLayer(uint32 LayerId)
|
|
{
|
|
OculusXRHMD::CheckInGameThread();
|
|
|
|
FPassthroughLayerPtr* LayerFound = LayerMap.Find(LayerId);
|
|
if (LayerFound)
|
|
{
|
|
(*LayerFound)->DestroyLayer();
|
|
}
|
|
LayerMap.Remove(LayerId);
|
|
}
|
|
|
|
void FPassthroughXR::OnSetLayerDesc(uint32 LayerId, const IStereoLayers::FLayerDesc& InLayerDesc)
|
|
{
|
|
OculusXRHMD::CheckInGameThread();
|
|
|
|
FPassthroughLayerPtr* LayerFound = LayerMap.Find(LayerId);
|
|
|
|
if (LayerFound)
|
|
{
|
|
(*LayerFound)->SetDesc(InLayerDesc);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void FPassthroughXR::InitializePassthrough(XrSession InSession)
|
|
{
|
|
if (bPassthroughInitialized)
|
|
return;
|
|
|
|
bPassthroughInitialized = true;
|
|
|
|
check(IsInRenderingThread());
|
|
|
|
const XrPassthroughCreateInfoFB PassthroughCreateInfo = { XR_TYPE_PASSTHROUGH_CREATE_INFO_FB };
|
|
|
|
XrResult CreatePassthroughResult = xrCreatePassthroughFB(InSession, &PassthroughCreateInfo, &PassthroughInstance);
|
|
if (!XR_SUCCEEDED(CreatePassthroughResult))
|
|
{
|
|
UE_LOG(LogOculusXRPassthrough, Error, TEXT("xrCreatePassthroughFB failed, error : %i"), CreatePassthroughResult);
|
|
return;
|
|
}
|
|
|
|
XrResult PassthroughStartResult = xrPassthroughStartFB(PassthroughInstance);
|
|
if (!XR_SUCCEEDED(PassthroughStartResult))
|
|
{
|
|
UE_LOG(LogOculusXRPassthrough, Error, TEXT("xrPassthroughStartFB failed, error : %i"), PassthroughStartResult);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void FPassthroughXR::ShutdownPassthrough(XrSession InSession)
|
|
{
|
|
if (!bPassthroughInitialized)
|
|
return;
|
|
|
|
bPassthroughInitialized = false;
|
|
|
|
check(IsInRenderingThread());
|
|
|
|
if (PassthroughInstance != XR_NULL_HANDLE)
|
|
{
|
|
XrResult Result = xrDestroyPassthroughFB(PassthroughInstance);
|
|
if (!XR_SUCCEEDED(Result))
|
|
{
|
|
UE_LOG(LogOculusXRPassthrough, Error, TEXT("xrDestroyPassthroughFB failed, error : %i"), Result);
|
|
}
|
|
PassthroughInstance = nullptr;
|
|
}
|
|
}
|
|
|
|
void FPassthroughXR::Update_GameThread(XrSession InSession)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
check(Settings != nullptr);
|
|
const bool bPassthroughEnabled = Settings->bPassthroughEnabled;
|
|
|
|
ExecuteOnRenderThread_DoNotWait([this, InSession, bPassthroughEnabled](FRHICommandListImmediate& RHICmdList) {
|
|
if (bPassthroughEnabled && !bPassthroughInitialized)
|
|
{
|
|
InitializePassthrough(InSession);
|
|
}
|
|
|
|
if (!bPassthroughEnabled && bPassthroughInitialized)
|
|
{
|
|
ShutdownPassthrough(InSession);
|
|
}
|
|
});
|
|
}
|
|
|
|
} // namespace XRPassthrough
|
|
|
|
#undef LOCTEXT_NAMESPACE
|