Android build settings + metaxr

This commit is contained in:
2025-05-14 14:00:02 +03:00
parent 6a2bb7475e
commit d5aa21f55c
594 changed files with 200530 additions and 2 deletions

View File

@@ -0,0 +1,369 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRPassthroughColorLut.h"
#include "OculusXRPassthroughXR.h"
#include "OculusXRPassthroughLayerComponent.h"
#include "OculusXRPassthroughXRFunctions.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRHMDModule.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#include "Math/UnrealMathUtility.h"
#include "GenericPlatform/GenericPlatformMath.h"
#include "UObject/ObjectSaveContext.h"
#include "OculusXRHMD.h"
#include "TextureResource.h"
namespace
{
ovrpPassthroughColorLutChannels ToOVRPColorLutChannels(EColorLutChannels InColorLutChannels)
{
switch (InColorLutChannels)
{
case ColorLutChannels_RGB:
return ovrpPassthroughColorLutChannels_Rgb;
case ColorLutChannels_RGBA:
return ovrpPassthroughColorLutChannels_Rgba;
default:
return ovrpPassthroughColorLutChannels_Invalid;
}
}
TArray<uint8> ColorArrayToColorData(const TArray<FColor>& InColorArray, bool IgnoreAlphaChannel)
{
TArray<uint8> Data;
const size_t ElementSize = IgnoreAlphaChannel ? 3 : 4;
Data.SetNum(InColorArray.Num() * ElementSize);
uint8* Dest = Data.GetData();
for (size_t i = 0; i < InColorArray.Num(); i++)
{
Data[i * ElementSize + 0] = InColorArray[i].R;
Data[i * ElementSize + 1] = InColorArray[i].G;
Data[i * ElementSize + 2] = InColorArray[i].B;
if (!IgnoreAlphaChannel)
{
Data[i * ElementSize + 3] = InColorArray[i].A;
}
}
return Data;
}
bool IsTextureDataValid(const FLutTextureData& Data)
{
return Data.Data.Num() > 0 && Data.Resolution > 0;
}
} // namespace
void UOculusXRPassthroughColorLut::SetLutFromArray(const TArray<FColor>& InColorArray, bool InIgnoreAlphaChannel)
{
const int32 Size = InColorArray.Num();
const int32 Resolution = FPlatformMath::RoundToInt(FPlatformMath::Pow(Size, 1.0 / 3));
if (Resolution > GetMaxResolution())
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Setting array ignored: Resoluton is exceeding maximum resoluton of %d."), GetMaxResolution());
return;
}
if (Resolution * Resolution * Resolution != Size)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Setting array ignored: Provided array size is not cube."));
return;
}
/* Check if size if power of 2 */
if ((Size & (Size - 1)) != 0)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Setting array ignored: Provided array does not result in a resolution that is a power of two."));
return;
}
ColorLutType = EColorLutType::Array;
const TArray<uint8>& Data = ColorArrayToColorData(InColorArray, InIgnoreAlphaChannel);
if (LutHandle == 0)
{
LutHandle = CreateLutObject(Data, Resolution);
return;
}
if (InIgnoreAlphaChannel == IgnoreAlphaChannel && Resolution == ColorArrayResolution)
{
UpdateLutObject(LutHandle, Data);
return;
}
DestroyLutObject(LutHandle);
LutHandle = CreateLutObject(Data, Resolution);
IgnoreAlphaChannel = InIgnoreAlphaChannel;
ColorArrayResolution = Resolution;
}
uint64 UOculusXRPassthroughColorLut::GetHandle(UOculusXRPassthroughLayerBase* LayerRef)
{
if (LutHandle == 0 && ColorLutType == EColorLutType::TextureLUT && IsTextureDataValid(StoredTextureData))
{
LutHandle = CreateLutObject(StoredTextureData.Data, StoredTextureData.Resolution);
}
// Add layer to reference list
LayerRefs.AddUnique(LayerRef->GetUniqueID());
return LutHandle;
}
void UOculusXRPassthroughColorLut::PreSave(FObjectPreSaveContext ObjectSaveContext)
{
Super::PreSave(ObjectSaveContext);
#if WITH_EDITOR
StoredTextureData = TextureToColorData(LutTexture);
#endif
}
void UOculusXRPassthroughColorLut::RemoveReference(UOculusXRPassthroughLayerBase* LayerRef)
{
LayerRefs.Remove(LayerRef->GetUniqueID());
if (LayerRefs.Num() == 0)
{
DestroyLutObject(LutHandle);
LutHandle = 0;
}
}
FLutTextureData UOculusXRPassthroughColorLut::TextureToColorData(class UTexture2D* InLutTexture) const
{
if (ColorLutType != EColorLutType::TextureLUT)
{
return FLutTextureData();
}
if (InLutTexture == nullptr)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Ignoring provided LUT texture. Provided texture is NULL."));
return FLutTextureData();
}
if (InLutTexture->LODGroup != TextureGroup::TEXTUREGROUP_ColorLookupTable)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Ignoring provided LUT texture. Provided texture is not LUT texture."));
return FLutTextureData();
}
if (InLutTexture->GetPlatformData()->Mips.Num() <= 0)
{
if (IsTextureDataValid(StoredTextureData))
{
// We do not need to save it again. Use previously saved data.
return StoredTextureData;
}
return FLutTextureData();
}
const uint32 TextureWidth = InLutTexture->GetImportedSize().X;
const uint32 TextureHeight = InLutTexture->GetImportedSize().Y;
uint32 ColorMapSize;
uint32 SlicesPerRow;
if (TextureWidth == TextureHeight)
{
float EdgeLength = FPlatformMath::Pow(TextureWidth, 2.0f / 3.0f);
ColorMapSize = FPlatformMath::RoundToInt(EdgeLength);
if (FPlatformMath::Abs(EdgeLength - ColorMapSize) > ZERO_ANIMWEIGHT_THRESH)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("LUT width and height are equal but don't correspond to an 'exploded cube'"));
return FLutTextureData();
}
SlicesPerRow = FPlatformMath::Sqrt(ColorMapSize * 1.0f);
}
else
{
if (TextureWidth != TextureHeight * TextureHeight)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("For rectangular LUTs, the width is expected to be equal to edgeLength^2"));
return FLutTextureData();
}
ColorMapSize = TextureHeight;
SlicesPerRow = TextureHeight;
}
FTexture2DMipMap& MipMap = InLutTexture->GetPlatformData()->Mips[0];
FByteBulkData* BulkData = &MipMap.BulkData;
const FColor* FormatedImageData = reinterpret_cast<const FColor*>(BulkData->Lock(LOCK_READ_ONLY));
TArray<FColor> Colors;
Colors.SetNum(ColorMapSize * ColorMapSize * ColorMapSize);
for (uint32 bi = 0; bi < ColorMapSize; bi++)
{
uint32 bi_row = bi % SlicesPerRow;
uint32 bi_col = bi / SlicesPerRow;
for (uint32 gi = 0; gi < ColorMapSize; gi++)
{
for (uint32 ri = 0; ri < ColorMapSize; ri++)
{
uint32 sX = ri + bi_row * ColorMapSize;
uint32 sY = gi + bi_col * ColorMapSize;
Colors[bi * ColorMapSize * ColorMapSize + gi * ColorMapSize + ri] = FormatedImageData[sX + sY * TextureWidth];
}
}
}
BulkData->Unlock();
return FLutTextureData(ColorArrayToColorData(Colors, IgnoreAlphaChannel), ColorMapSize);
}
uint64 UOculusXRPassthroughColorLut::CreateLutObject(const TArray<uint8>& InData, uint32 Resolution) const
{
if (OculusXR::IsOpenXRSystem())
{
TWeakPtr<XRPassthrough::FPassthroughXR> Passthrough = XRPassthrough::FPassthroughXR::GetInstance();
if (!Passthrough.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("Couldn't retrieve passthrough plugin extension."));
return 0;
}
if (!Passthrough.Pin()->GetSettings()->bExtColorLutAvailable)
{
UE_LOG(LogTemp, Warning, TEXT("XR_META_passthrough_color_lut extension is not available."));
return 0;
}
XrPassthroughFB PassthroughHandle = Passthrough.Pin()->GetPassthroughInstance();
if (PassthroughHandle == XR_NULL_HANDLE)
{
UE_LOG(LogTemp, Error, TEXT("Passthrough handle is null."));
return 0;
}
XrPassthroughColorLutCreateInfoMETA createInfo = { XR_TYPE_PASSTHROUGH_COLOR_LUT_CREATE_INFO_META };
createInfo.channels = IgnoreAlphaChannel ? XR_PASSTHROUGH_COLOR_LUT_CHANNELS_RGB_META : XR_PASSTHROUGH_COLOR_LUT_CHANNELS_RGBA_META;
createInfo.resolution = Resolution;
XrPassthroughColorLutDataMETA lutData;
lutData.bufferSize = InData.Num();
lutData.buffer = InData.GetData();
createInfo.data = lutData;
XrPassthroughColorLutMETA outLut = XR_NULL_HANDLE;
if (XR_FAILED(XRPassthrough::xrCreatePassthroughColorLutMETA(PassthroughHandle, &createInfo, &outLut)))
{
UE_LOG(LogTemp, Error, TEXT("Failed creating passthrough color lut."));
return 0;
}
return reinterpret_cast<uint64_t>(outLut);
}
else
{
ovrpPassthroughColorLutData OVRPData;
OVRPData.Buffer = InData.GetData();
OVRPData.BufferSize = InData.Num();
const EColorLutChannels Channels = IgnoreAlphaChannel ? EColorLutChannels::ColorLutChannels_RGB : EColorLutChannels::ColorLutChannels_RGBA;
ovrpPassthroughColorLut Handle;
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().CreatePassthroughColorLut(
ToOVRPColorLutChannels(Channels),
Resolution,
OVRPData,
&Handle)))
{
UE_LOG(LogTemp, Error, TEXT("Failed creating passthrough color lut."));
return 0;
}
return Handle;
}
}
void UOculusXRPassthroughColorLut::UpdateLutObject(uint64 Handle, const TArray<uint8>& InData) const
{
if (Handle == 0)
{
return;
}
if (OculusXR::IsOpenXRSystem())
{
XrPassthroughColorLutUpdateInfoMETA UpdateInfo = { XR_TYPE_PASSTHROUGH_COLOR_LUT_UPDATE_INFO_META };
XrPassthroughColorLutDataMETA LutData;
LutData.bufferSize = InData.Num();
LutData.buffer = reinterpret_cast<const uint8_t*>(InData.GetData());
UpdateInfo.data = LutData;
if (XR_FAILED(XRPassthrough::xrUpdatePassthroughColorLutMETA(reinterpret_cast<XrPassthroughColorLutMETA>(Handle), &UpdateInfo)))
{
UE_LOG(LogTemp, Error, TEXT("Failed updating passthrough color lut data."));
return;
}
}
else
{
ovrpPassthroughColorLutData OVRPData;
OVRPData.Buffer = InData.GetData();
OVRPData.BufferSize = InData.Num();
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().UpdatePassthroughColorLut(
Handle,
OVRPData)))
{
UE_LOG(LogTemp, Error, TEXT("Failed updating passthrough color lut data."));
return;
}
}
}
void UOculusXRPassthroughColorLut::DestroyLutObject(uint64 Handle) const
{
if (Handle == 0)
{
return;
}
if (OculusXR::IsOpenXRSystem())
{
if (XR_FAILED(XRPassthrough::xrDestroyPassthroughColorLutMETA(reinterpret_cast<XrPassthroughColorLutMETA>(Handle))))
{
UE_LOG(LogTemp, Error, TEXT("Failed to destroy passthrough color lut."));
}
}
else
{
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().DestroyPassthroughColorLut(Handle)))
{
UE_LOG(LogTemp, Error, TEXT("Failed to destroy passthrough color lut."));
}
}
}
void UOculusXRPassthroughColorLut::BeginDestroy()
{
Super::BeginDestroy();
DestroyLutObject(LutHandle);
LutHandle = 0;
}
int UOculusXRPassthroughColorLut::GetMaxResolution()
{
if (MaxResolution > -1)
{
return MaxResolution;
}
ovrpInsightPassthroughCapabilities PassthroughCapabilites;
PassthroughCapabilites.Fields =
static_cast<ovrpInsightPassthroughCapabilityFields>(
ovrpInsightPassthroughCapabilityFields::ovrpInsightPassthroughCapabilityFields_Flags | ovrpInsightPassthroughCapabilityFields::ovrpInsightPassthroughCapabilityFields_MaxColorLutResolution);
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetPassthroughCapabilities(&PassthroughCapabilites)))
{
UE_LOG(LogTemp, Error, TEXT("Failed to fetch passthrough capabilities."));
// Default MAX resoulution is 64.
return 64;
}
MaxResolution = PassthroughCapabilites.MaxColorLutResolution;
return MaxResolution;
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRPassthroughEventHandling.h"
#include "OculusXRHMD.h"
#include "OculusXRPassthroughModule.h"
#include "OculusXRPassthroughSubsystem.h"
namespace OculusXRPassthrough
{
FOculusXRPassthroughEventDelegates::FOculusXRPassthroughLayerResumedDelegate FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed;
template <typename T>
void GetEventData(ovrpEventDataBuffer& Buffer, T& OutEventData)
{
unsigned char* BufData = Buffer.EventData;
BufData -= sizeof(Buffer.EventType); // Offset buffer data to get to the actual event payload
memcpy(&OutEventData, BufData, sizeof(T));
}
void FOculusXRPassthroughEventHandling::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult)
{
ovrpEventDataBuffer& buf = *EventDataBuffer;
EventPollResult = true;
switch (buf.EventType)
{
case ovrpEventType_PassthroughLayerResumed:
{
OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
check(HMD);
ovrpEventDataPassthroughLayerResumed passthroughLayerResumedEvent;
GetEventData(buf, passthroughLayerResumedEvent);
// Convert OVR plugin layerID to UE layerID
int ovrpID = passthroughLayerResumedEvent.LayerId;
uint32 LayerID = HMD->GetLayerIdFromOvrpId(ovrpID);
UE_LOG(LogOculusXRPassthrough, Log, TEXT("FOculusXRPassthroughEventHandling - Passthrough Layer #%d resumed"), LayerID);
// Send event
FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed.Broadcast(LayerID);
break;
}
case ovrpEventType_None:
default:
{
EventPollResult = false;
break;
}
}
}
} // namespace OculusXRPassthrough

View File

@@ -0,0 +1,33 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRPluginWrapper.h"
#include "CoreMinimal.h"
#include "OculusXRHMDPrivate.h"
namespace OculusXRPassthrough
{
class FOculusXRPassthroughEventDelegates
{
public:
/* ovrpEventType_PassthroughLayerResumed
*
* PassthroughLayerResumed
* Prefix:
* FOculusXRPassthroughLayerResumed
* Suffix:
* FOculusXRPassthroughLayerResumedDelegate
*/
DECLARE_MULTICAST_DELEGATE_OneParam(FOculusXRPassthroughLayerResumedDelegate, int /*layerId*/);
static OCULUSXRPASSTHROUGH_API FOculusXRPassthroughLayerResumedDelegate OculusPassthroughLayerResumed;
};
struct FOculusXRPassthroughEventHandling
{
public:
static void OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult);
};
} // namespace OculusXRPassthrough

View File

@@ -0,0 +1,657 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRPassthroughLayer.h"
#include "Components/MeshComponent.h"
#include "Engine/Engine.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Materials/MaterialInterface.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRPassthroughXRFunctions.h"
#include "OculusXRPassthroughXR.h"
#include "OculusXRPassthroughModule.h"
#include "ProceduralMeshComponent.h"
#include "XRThreadUtils.h"
namespace XRPassthrough
{
static UWorld* GetWorld()
{
UWorld* World = nullptr;
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
if (Context.WorldType == EWorldType::Game || Context.WorldType == EWorldType::PIE)
{
World = Context.World();
}
}
return World;
}
FPassthroughLayer::FPassthroughLayer(XrPassthroughFB PassthroughInstance, TWeakPtr<FPassthroughXR> Extension)
: PassthroughExtension(Extension)
, UserDefinedGeometryMap(nullptr)
, PassthroughPokeActorMap(nullptr)
, XrPassthroughLayer{ XR_NULL_HANDLE }
, XrCompositionLayerHeader{}
, XrPassthroughInstance(PassthroughInstance)
{
}
FPassthroughLayer::FPassthroughLayer(const FPassthroughLayer& Layer)
: PassthroughExtension(Layer.PassthroughExtension)
, UserDefinedGeometryMap(Layer.UserDefinedGeometryMap)
, PassthroughPokeActorMap(Layer.PassthroughPokeActorMap)
, Session(Layer.Session)
, LayerDesc(Layer.LayerDesc)
, XrPassthroughLayer(Layer.XrPassthroughLayer)
, XrCompositionLayerHeader(Layer.XrCompositionLayerHeader)
, XrPassthroughInstance(Layer.XrPassthroughInstance)
{
}
TSharedPtr<FPassthroughLayer, ESPMode::ThreadSafe> FPassthroughLayer::Clone() const
{
return MakeShareable(new FPassthroughLayer(*this));
}
FPassthroughLayer::~FPassthroughLayer()
{
}
void FPassthroughLayer::SetDesc(const IStereoLayers::FLayerDesc& InLayerDesc)
{
LayerDesc = InLayerDesc;
if (!PassthroughPokeActorMap)
{
PassthroughPokeActorMap = MakeShared<TMap<FString, FPassthroughPokeActor>, ESPMode::ThreadSafe>();
}
UpdatePassthroughPokeActors_GameThread();
}
void FPassthroughLayer::DestroyLayer()
{
OculusXRHMD::CheckInGameThread();
ClearPassthroughPokeActors();
}
void FPassthroughLayer::DestroyLayer_RenderThread()
{
OculusXRHMD::CheckInRenderThread();
// Clear user defined meshes
for (auto& Entry : *UserDefinedGeometryMap)
{
const XrTriangleMeshFB MeshHandle = Entry.Value.MeshHandle;
const XrGeometryInstanceFB InstanceHandle = Entry.Value.InstanceHandle;
RemovePassthroughMesh_RenderThread(MeshHandle, InstanceHandle);
}
UserDefinedGeometryMap->Empty();
// Destroy passthrough layer
if (XrPassthroughLayer != XR_NULL_HANDLE)
{
xrDestroyPassthroughLayerFB(XrPassthroughLayer);
}
else
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to destroy layer as handle was null"));
}
}
bool FPassthroughLayer::IsPassthoughLayerDesc(const IStereoLayers::FLayerDesc& LayerDesc)
{
return LayerDesc.HasShape<FReconstructedLayer>() || LayerDesc.HasShape<FUserDefinedLayer>();
}
bool FPassthroughLayer::CanReuseResources(const FPassthroughLayer* InLayer) const
{
if (!InLayer)
{
return false;
}
if (!IsPassthoughLayerDesc(InLayer->LayerDesc) || InLayer->LayerDesc.HasShape<FReconstructedLayer>() != LayerDesc.HasShape<FReconstructedLayer>() || InLayer->LayerDesc.HasShape<FUserDefinedLayer>() != LayerDesc.HasShape<FUserDefinedLayer>())
{
return false;
}
return true;
}
bool FPassthroughLayer::Initialize_RenderThread(XrSession InSession, const FPassthroughLayer* InLayer)
{
OculusXRHMD::CheckInRenderThread();
if (!CanReuseResources(InLayer))
{
Session = InSession;
if (XrPassthroughLayer != XR_NULL_HANDLE)
{
xrDestroyPassthroughLayerFB(XrPassthroughLayer);
XrPassthroughLayer = XR_NULL_HANDLE;
}
if (LayerDesc.HasShape<FReconstructedLayer>() || LayerDesc.HasShape<FUserDefinedLayer>())
{
XrPassthroughLayerCreateInfoFB PassthroughLayerCreateInfo = { XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB };
PassthroughLayerCreateInfo.passthrough = XrPassthroughInstance;
PassthroughLayerCreateInfo.purpose = LayerDesc.HasShape<FReconstructedLayer>() ? XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB : XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB;
XrResult CreateLayerResult = xrCreatePassthroughLayerFB(Session, &PassthroughLayerCreateInfo, &XrPassthroughLayer);
if (!XR_SUCCEEDED(CreateLayerResult))
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to create passthrough layer, error : %i"), CreateLayerResult);
return false;
}
XrResult ResumeLayerResult = xrPassthroughLayerResumeFB(XrPassthroughLayer);
if (!XR_SUCCEEDED(ResumeLayerResult))
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to resume passthrough layer, error : %i"), ResumeLayerResult);
return false;
}
}
}
else
{
PassthroughExtension = InLayer->PassthroughExtension;
UserDefinedGeometryMap = InLayer->UserDefinedGeometryMap;
PassthroughPokeActorMap = InLayer->PassthroughPokeActorMap;
Session = InLayer->Session;
XrPassthroughLayer = InLayer->XrPassthroughLayer;
XrCompositionLayerHeader = InLayer->XrCompositionLayerHeader;
XrPassthroughInstance = InLayer->XrPassthroughInstance;
}
if (!UserDefinedGeometryMap)
{
UserDefinedGeometryMap = MakeShared<TMap<FString, FPassthroughMesh>, ESPMode::ThreadSafe>();
}
check(IsPassthoughLayerDesc(LayerDesc));
return true;
}
bool FPassthroughLayer::BuildPassthroughPokeActor(OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FPassthroughPokeActor& OutPassthroughPokeActor)
{
UWorld* World = GetWorld();
if (!World || !PassthroughExtension.IsValid())
{
return false;
}
const FString BaseComponentName = FString::Printf(TEXT("OculusPassthroughPoke_%d"), LayerDesc.Id);
const FName ComponentName(*BaseComponentName);
AActor* PassthoughPokeActor = World->SpawnActor<AActor>();
UProceduralMeshComponent* PassthoughPokeComponentPtr = NewObject<UProceduralMeshComponent>(PassthoughPokeActor, ComponentName);
PassthoughPokeComponentPtr->RegisterComponent();
const TArray<int32>& Triangles = PassthroughMesh->GetTriangles();
const TArray<FVector>& Vertices = PassthroughMesh->GetVertices();
TArray<FVector> Normals;
TArray<FVector2D> UV0;
TArray<FLinearColor> VertexColors;
TArray<FProcMeshTangent> Tangents;
PassthoughPokeComponentPtr->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, false);
UMaterial* PokeAHoleMaterial = PassthroughExtension.Pin()->GetSettings()->PokeAHoleMaterial;
if (PokeAHoleMaterial)
{
UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(PokeAHoleMaterial, nullptr);
PassthoughPokeComponentPtr->SetMaterial(0, DynamicMaterial);
}
OutPassthroughPokeActor.PokeAHoleActor = PassthoughPokeActor;
OutPassthroughPokeActor.PokeAHoleComponentPtr = PassthoughPokeComponentPtr;
return true;
}
void FPassthroughLayer::UpdatePassthroughPokeActors_GameThread()
{
if (LayerDesc.HasShape<FUserDefinedLayer>())
{
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
const TArray<FUserDefinedGeometryDesc>& UserGeometryList = UserDefinedLayerProps.UserGeometryList;
TSet<FString> UsedSet = {};
if (PassthroughSupportsDepth())
{
for (const FUserDefinedGeometryDesc& GeometryDesc : UserGeometryList)
{
const FString MeshName = GeometryDesc.MeshName;
UsedSet.Add(MeshName);
FPassthroughPokeActor* FoundPassthroughPokeActor = PassthroughPokeActorMap->Find(MeshName);
if (!FoundPassthroughPokeActor)
{
OculusXRHMD::FOculusPassthroughMeshRef GeomPassthroughMesh = GeometryDesc.PassthroughMesh;
if (GeomPassthroughMesh)
{
FPassthroughPokeActor PassthroughPokeActor;
if (BuildPassthroughPokeActor(GeomPassthroughMesh, PassthroughPokeActor))
{
PassthroughPokeActor.PokeAHoleComponentPtr->SetWorldTransform(GeometryDesc.Transform);
PassthroughPokeActorMap->Add(MeshName, PassthroughPokeActor);
}
}
}
else if (GeometryDesc.bUpdateTransform && FoundPassthroughPokeActor->PokeAHoleComponentPtr.IsValid())
{
FoundPassthroughPokeActor->PokeAHoleComponentPtr->SetWorldTransform(GeometryDesc.Transform);
}
}
}
// find actors that no longer exist
TArray<FString> ItemsToRemove;
for (auto& Entry : *PassthroughPokeActorMap)
{
if (!UsedSet.Contains(Entry.Key))
{
ItemsToRemove.Add(Entry.Key);
}
}
for (FString Entry : ItemsToRemove)
{
FPassthroughPokeActor* PassthroughPokeActor = PassthroughPokeActorMap->Find(Entry);
if (PassthroughPokeActor)
{
UWorld* World = GetWorld();
if (World && PassthroughPokeActor->PokeAHoleActor.IsValid())
{
World->DestroyActor(PassthroughPokeActor->PokeAHoleActor.Get());
}
}
PassthroughPokeActorMap->Remove(Entry);
}
}
}
void FPassthroughLayer::UpdatePassthroughStyle_RenderThread(const FEdgeStyleParameters& EdgeStyleParameters)
{
if (!PassthroughExtension.IsValid())
{
return;
}
XrPassthroughStyleFB Style = { XR_TYPE_PASSTHROUGH_STYLE_FB };
Style.textureOpacityFactor = EdgeStyleParameters.TextureOpacityFactor;
Style.edgeColor = { 0, 0, 0, 0 };
if (EdgeStyleParameters.bEnableEdgeColor)
{
Style.edgeColor = {
EdgeStyleParameters.EdgeColor.R,
EdgeStyleParameters.EdgeColor.G,
EdgeStyleParameters.EdgeColor.B,
EdgeStyleParameters.EdgeColor.A
};
}
/// Color map
union AllColorMapDescriptors
{
XrPassthroughColorMapMonoToRgbaFB rgba;
XrPassthroughColorMapMonoToMonoFB mono;
XrPassthroughBrightnessContrastSaturationFB bcs;
XrPassthroughColorMapLutMETA lut;
XrPassthroughColorMapInterpolatedLutMETA interpLut;
};
AllColorMapDescriptors colorMap;
if (PassthroughExtension.Pin()->GetSettings()->bExtColorLutAvailable && EdgeStyleParameters.bEnableColorMap)
{
void* colorMapDataDestination = nullptr;
unsigned int expectedColorMapDataSize = 0;
switch (EdgeStyleParameters.ColorMapType)
{
case ColorMapType_None:
break;
case ColorMapType_GrayscaleToColor:
colorMap.rgba = { XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB };
expectedColorMapDataSize = sizeof(colorMap.rgba.textureColorMap);
colorMapDataDestination = colorMap.rgba.textureColorMap;
Style.next = &colorMap.rgba;
break;
case ColorMapType_Grayscale:
colorMap.mono = { XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB };
expectedColorMapDataSize = sizeof(colorMap.mono.textureColorMap);
colorMapDataDestination = colorMap.mono.textureColorMap;
Style.next = &colorMap.mono;
break;
case ColorMapType_ColorAdjustment:
colorMap.bcs = { XR_TYPE_PASSTHROUGH_BRIGHTNESS_CONTRAST_SATURATION_FB };
expectedColorMapDataSize = 3 * sizeof(float);
colorMapDataDestination = &colorMap.bcs.brightness;
Style.next = &colorMap.bcs;
break;
case ColorMapType_ColorLut:
colorMap.lut = { XR_TYPE_PASSTHROUGH_COLOR_MAP_LUT_META };
colorMap.lut.colorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[0]);
colorMap.lut.weight = EdgeStyleParameters.ColorLutDesc.Weight;
Style.next = &colorMap.lut;
break;
case ColorMapType_ColorLut_Interpolated:
colorMap.interpLut = { XR_TYPE_PASSTHROUGH_COLOR_MAP_INTERPOLATED_LUT_META };
colorMap.interpLut.sourceColorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[0]);
colorMap.interpLut.targetColorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[1]);
colorMap.interpLut.weight = EdgeStyleParameters.ColorLutDesc.Weight;
Style.next = &colorMap.lut;
break;
default:
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Passthrough style has unexpected color map type: %i"), EdgeStyleParameters.ColorMapType);
return;
}
// Validate color map data size and copy it over
if (colorMapDataDestination != nullptr)
{
if (EdgeStyleParameters.ColorMapData.Num() != expectedColorMapDataSize)
{
UE_LOG(LogOculusXRPassthrough, Error,
TEXT("Passthrough color map size for type %i is expected to be %i instead of %i"),
EdgeStyleParameters.ColorMapType,
expectedColorMapDataSize,
EdgeStyleParameters.ColorMapData.Num());
return;
}
uint8* ColorMapData = (uint8*)EdgeStyleParameters.ColorMapData.GetData();
memcpy(colorMapDataDestination, ColorMapData, expectedColorMapDataSize);
}
}
XrResult Result = xrPassthroughLayerSetStyleFB(XrPassthroughLayer, &Style);
if (!XR_SUCCEEDED(Result))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed setting passthrough style, error : %i"), Result);
return;
}
}
static FMatrix TransformToPassthroughSpace(FTransform Transform, float WorldToMetersScale, FTransform TrackingToWorld)
{
const FVector WorldToMetersScaleInv = FVector(WorldToMetersScale).Reciprocal();
FTransform TransformWorld = Transform * TrackingToWorld.Inverse();
TransformWorld.MultiplyScale3D(WorldToMetersScaleInv);
TransformWorld.ScaleTranslation(WorldToMetersScaleInv);
const FMatrix TransformWorldScaled = TransformWorld.ToMatrixWithScale();
const FMatrix SwapAxisMatrix(
FPlane(0.0f, 0.0f, -1.0f, 0.0f),
FPlane(1.0f, 0.0f, 0.0f, 0.0f),
FPlane(0.0f, 1.0f, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, 0.0f, 1.0f));
return TransformWorldScaled * SwapAxisMatrix;
}
void FPassthroughLayer::UpdatePassthrough_RenderThread(FRHICommandListImmediate& RHICmdList, XrSpace Space, XrTime Time, float WorldToMetersScale, FTransform TrackingToWorld)
{
check(IsInRenderingThread());
if (LayerDesc.HasShape<FReconstructedLayer>())
{
const FReconstructedLayer& ReconstructedLayerProps = LayerDesc.GetShape<FReconstructedLayer>();
UpdatePassthroughStyle_RenderThread(ReconstructedLayerProps.EdgeStyleParameters);
}
else if (LayerDesc.HasShape<FUserDefinedLayer>())
{
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
UpdatePassthroughStyle_RenderThread(UserDefinedLayerProps.EdgeStyleParameters);
}
if (LayerDesc.HasShape<FUserDefinedLayer>())
{
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
const TArray<FUserDefinedGeometryDesc>& UserGeometryList = UserDefinedLayerProps.UserGeometryList;
TSet<FString> UsedSet;
for (const FUserDefinedGeometryDesc& GeometryDesc : UserGeometryList)
{
const FString MeshName = GeometryDesc.MeshName;
UsedSet.Add(MeshName);
FPassthroughMesh* LayerPassthroughMesh = UserDefinedGeometryMap->Find(MeshName);
if (!LayerPassthroughMesh)
{
OculusXRHMD::FOculusPassthroughMeshRef GeomPassthroughMesh = GeometryDesc.PassthroughMesh;
if (GeomPassthroughMesh)
{
const FMatrix Transform = TransformToPassthroughSpace(GeometryDesc.Transform, WorldToMetersScale, TrackingToWorld);
XrTriangleMeshFB MeshHandle = 0;
XrGeometryInstanceFB InstanceHandle = 0;
AddPassthroughMesh_RenderThread(GeomPassthroughMesh->GetVertices(), GeomPassthroughMesh->GetTriangles(), Transform, Space, MeshHandle, InstanceHandle);
UserDefinedGeometryMap->Add(MeshName, FPassthroughMesh(MeshHandle, InstanceHandle, GeometryDesc.Transform));
}
}
else
{
const FMatrix Transform = TransformToPassthroughSpace(GeometryDesc.Transform, WorldToMetersScale, TrackingToWorld);
UpdatePassthroughMeshTransform_RenderThread(LayerPassthroughMesh->InstanceHandle, Transform, Space, Time);
LayerPassthroughMesh->LastTransform = GeometryDesc.Transform;
}
}
// find meshes that no longer exist
TArray<FString> ItemsToRemove;
for (auto& Entry : *UserDefinedGeometryMap)
{
if (!UsedSet.Contains(Entry.Key))
{
ItemsToRemove.Add(Entry.Key);
}
}
for (FString Entry : ItemsToRemove)
{
FPassthroughMesh* PassthroughMesh = UserDefinedGeometryMap->Find(Entry);
if (PassthroughMesh)
{
const XrTriangleMeshFB MeshHandle = PassthroughMesh->MeshHandle;
const XrGeometryInstanceFB InstanceHandle = PassthroughMesh->InstanceHandle;
RemovePassthroughMesh_RenderThread(MeshHandle, InstanceHandle);
}
else
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("PassthroughMesh: %s doesn't exist."), *Entry);
return;
}
UserDefinedGeometryMap->Remove(Entry);
}
}
}
XrCompositionLayerBaseHeaderType* FPassthroughLayer::GetXrCompositionLayerHeader()
{
OculusXRHMD::CheckInRHIThread();
if (XrPassthroughLayer != nullptr)
{
XrCompositionLayerPassthroughFB& CompositionLayer = XrCompositionLayerHeader;
memset(&CompositionLayer, 0, sizeof(CompositionLayer));
CompositionLayer.type = XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB;
CompositionLayer.layerHandle = XrPassthroughLayer;
CompositionLayer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
CompositionLayer.space = XR_NULL_HANDLE;
return reinterpret_cast<XrCompositionLayerBaseHeaderType*>(&CompositionLayer);
}
return nullptr;
}
bool FPassthroughLayer::IsBackgroundLayer() const
{
return (LayerDesc.HasShape<FReconstructedLayer>() && (LayerDesc.GetShape<FReconstructedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Underlay))
|| (LayerDesc.HasShape<FUserDefinedLayer>() && (LayerDesc.GetShape<FUserDefinedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Underlay));
}
bool FPassthroughLayer::IsOverlayLayer() const
{
return (LayerDesc.HasShape<FReconstructedLayer>() && (LayerDesc.GetShape<FReconstructedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Overlay))
|| (LayerDesc.HasShape<FUserDefinedLayer>() && (LayerDesc.GetShape<FUserDefinedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Overlay));
}
bool FPassthroughLayer::PassthroughSupportsDepth() const
{
return ((LayerDesc.Flags & IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH) != 0) && LayerDesc.HasShape<FUserDefinedLayer>();
}
// Code taken from OVRPlugin (InsightMrManager.cpp)
static bool DecomposeTransformMatrix(FMatrix Transform, XrPosef& OutPose, XrVector3f& OutScale)
{
FTransform outTransform = FTransform(Transform);
FVector3f scale = FVector3f(outTransform.GetScale3D());
FQuat4f rotation = FQuat4f(outTransform.GetRotation());
FVector3f position = FVector3f(outTransform.GetLocation());
if (scale.X == 0 || scale.Y == 0 || scale.Z == 0)
{
return false;
}
OutScale = XrVector3f{ scale.X, scale.Y, scale.Z };
OutPose = XrPosef{ XrQuaternionf{ rotation.X, rotation.Y, rotation.Z, rotation.W }, XrVector3f{ position.X, position.Y, position.Z } };
return true;
}
void FPassthroughLayer::AddPassthroughMesh_RenderThread(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, FMatrix Transformation, XrSpace Space, XrTriangleMeshFB& OutMeshHandle, XrGeometryInstanceFB& OutInstanceHandle)
{
OculusXRHMD::CheckInRenderThread();
XrTriangleMeshFB MeshHandle = 0;
XrGeometryInstanceFB InstanceHandle = 0;
// Explicit conversion is needed since FVector contains double elements.
// Converting Vertices.Data() to float* causes issues when memory is parsed.
TArray<XrVector3f> VertexData;
VertexData.SetNumUninitialized(Vertices.Num());
size_t i = 0;
for (const FVector& vertex : Vertices)
{
VertexData[i++] = { (float)vertex.X, (float)vertex.Y, (float)vertex.Z };
}
TArray<uint32_t> TriangleData;
TriangleData.SetNumUninitialized(Triangles.Num());
i = 0;
for (const int32& tri : Triangles)
{
TriangleData[i++] = (uint32_t)tri;
}
XrTriangleMeshCreateInfoFB TriangleMeshInfo = { XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB };
TriangleMeshInfo.flags = 0; // not mutable
TriangleMeshInfo.triangleCount = Triangles.Num() / 3;
TriangleMeshInfo.indexBuffer = TriangleData.GetData();
TriangleMeshInfo.vertexCount = Vertices.Num();
TriangleMeshInfo.vertexBuffer = VertexData.GetData();
TriangleMeshInfo.windingOrder = XR_WINDING_ORDER_UNKNOWN_FB;
if (XR_FAILED(xrCreateTriangleMeshFB.GetValue()(Session, &TriangleMeshInfo, &MeshHandle)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed creating passthrough mesh surface."));
return;
}
XrGeometryInstanceCreateInfoFB createInfo = { XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB };
bool result = DecomposeTransformMatrix(Transformation, createInfo.pose, createInfo.scale);
if (!result)
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed decomposing the transform matrix."));
return;
}
createInfo.layer = XrPassthroughLayer;
createInfo.mesh = MeshHandle;
createInfo.baseSpace = Space;
if (XR_FAILED(xrCreateGeometryInstanceFB(Session, &createInfo, &InstanceHandle)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed adding passthrough mesh surface to scene."));
return;
}
OutMeshHandle = MeshHandle;
OutInstanceHandle = InstanceHandle;
}
void FPassthroughLayer::UpdatePassthroughMeshTransform_RenderThread(XrGeometryInstanceFB InstanceHandle, FMatrix Transformation, XrSpace Space, XrTime Time)
{
OculusXRHMD::CheckInRenderThread();
XrGeometryInstanceTransformFB UpdateInfo = { XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB };
bool result = DecomposeTransformMatrix(Transformation, UpdateInfo.pose, UpdateInfo.scale);
if (!result)
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed decomposing the transform matrix."));
return;
}
UpdateInfo.baseSpace = Space;
UpdateInfo.time = Time;
if (XR_FAILED(xrGeometryInstanceSetTransformFB(InstanceHandle, &UpdateInfo)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed updating passthrough mesh surface transform."));
return;
}
}
void FPassthroughLayer::RemovePassthroughMesh_RenderThread(XrTriangleMeshFB MeshHandle, XrGeometryInstanceFB InstanceHandle)
{
OculusXRHMD::CheckInRenderThread();
if (XR_FAILED(xrDestroyGeometryInstanceFB(InstanceHandle)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed removing passthrough surface from scene."));
return;
}
if (XR_FAILED(xrDestroyTriangleMeshFB.GetValue()(MeshHandle)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed destroying passthrough surface mesh."));
return;
}
}
void FPassthroughLayer::ClearPassthroughPokeActors()
{
if (PassthroughPokeActorMap)
{
UWorld* World = GetWorld();
if (!World)
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Couldn't retrieve World. Passthrough Pokeahole actors will not be destroyed."));
return;
}
for (auto& Entry : *PassthroughPokeActorMap)
{
// Check if actor is still valid. In some specific cases the actor might be destroyed before we clean the PokeActorMap
// (e.g. when loading to a new level)
if (Entry.Value.PokeAHoleActor.IsValid())
{
World->DestroyActor(Entry.Value.PokeAHoleActor.Get());
}
}
PassthroughPokeActorMap.Reset();
}
}
} // namespace XRPassthrough

View File

@@ -0,0 +1,155 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
#include "IStereoLayers.h"
#include "OculusXRPassthroughLayerShapes.h"
#include "OculusXRPassthroughMesh.h"
class UProceduralMeshComponent;
namespace XRPassthrough
{
#ifdef WITH_OCULUS_BRANCH
using XrCompositionLayerBaseHeaderType = XrCompositionLayerBaseHeader;
#else
// epic branch has member as const
using XrCompositionLayerBaseHeaderType = const XrCompositionLayerBaseHeader;
#endif
class FPassthroughXR;
class FPassthroughLayer
{
private:
struct FPassthroughMesh
{
FPassthroughMesh(XrTriangleMeshFB MeshHandle, XrGeometryInstanceFB InstanceHandle, FTransform Transform)
: MeshHandle(MeshHandle)
, InstanceHandle(InstanceHandle)
, LastTransform(Transform)
{
}
XrTriangleMeshFB MeshHandle;
XrGeometryInstanceFB InstanceHandle;
FTransform LastTransform;
};
typedef TSharedPtr<TMap<FString, FPassthroughMesh>, ESPMode::ThreadSafe> FUserDefinedGeometryMapPtr;
struct FPassthroughPokeActor
{
FPassthroughPokeActor(){};
FPassthroughPokeActor(TWeakObjectPtr<UProceduralMeshComponent> PokeAHoleComponentPtr, TWeakObjectPtr<AActor> PokeAHoleActor)
: PokeAHoleComponentPtr(PokeAHoleComponentPtr)
, PokeAHoleActor(PokeAHoleActor){};
TWeakObjectPtr<UProceduralMeshComponent> PokeAHoleComponentPtr;
TWeakObjectPtr<AActor> PokeAHoleActor;
};
typedef TSharedPtr<TMap<FString, FPassthroughPokeActor>, ESPMode::ThreadSafe> FPassthroughPokeActorMapPtr;
public:
static bool IsPassthoughLayerDesc(const IStereoLayers::FLayerDesc& LayerDesc);
FPassthroughLayer(XrPassthroughFB PassthroughInstance, TWeakPtr<FPassthroughXR> Extension);
FPassthroughLayer(const FPassthroughLayer& Layer);
TSharedPtr<FPassthroughLayer, ESPMode::ThreadSafe> Clone() const;
virtual ~FPassthroughLayer();
void SetDesc(const IStereoLayers::FLayerDesc& InLayerDesc);
void DestroyLayer();
void DestroyLayer_RenderThread();
bool CanReuseResources(const FPassthroughLayer* InLayer) const;
bool Initialize_RenderThread(XrSession InSession, const FPassthroughLayer* InLayer = nullptr);
bool BuildPassthroughPokeActor(OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FPassthroughPokeActor& OutPassthroughPokeActor);
void UpdatePassthroughPokeActors_GameThread();
void UpdatePassthroughStyle_RenderThread(const FEdgeStyleParameters& EdgeStyleParameters);
void UpdatePassthrough_RenderThread(FRHICommandListImmediate& RHICmdList, XrSpace Space, XrTime Time, float WorldToMetersScale, FTransform TrackingToWorld);
XrCompositionLayerBaseHeaderType* GetXrCompositionLayerHeader();
bool IsBackgroundLayer() const;
bool IsOverlayLayer() const;
bool PassthroughSupportsDepth() const;
const IStereoLayers::FLayerDesc& GetDesc() const { return LayerDesc; };
const XrPassthroughLayerFB GetLayerHandle() const { return XrPassthroughLayer; }
void AddPassthroughMesh_RenderThread(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, FMatrix Transformation, XrSpace Space, XrTriangleMeshFB& OutMeshHandle, XrGeometryInstanceFB& OutInstanceHandle);
void UpdatePassthroughMeshTransform_RenderThread(XrGeometryInstanceFB InstanceHandle, FMatrix Transformation, XrSpace Space, XrTime Time);
void RemovePassthroughMesh_RenderThread(XrTriangleMeshFB MeshHandle, XrGeometryInstanceFB InstanceHandle);
void ClearPassthroughPokeActors();
private:
TWeakPtr<FPassthroughXR> PassthroughExtension;
FUserDefinedGeometryMapPtr UserDefinedGeometryMap;
FPassthroughPokeActorMapPtr PassthroughPokeActorMap;
XrSession Session;
IStereoLayers::FLayerDesc LayerDesc;
XrPassthroughLayerFB XrPassthroughLayer;
XrCompositionLayerPassthroughFB XrCompositionLayerHeader;
XrPassthroughFB XrPassthroughInstance;
};
typedef TSharedPtr<FPassthroughLayer, ESPMode::ThreadSafe> FPassthroughLayerPtr;
struct FPassthroughLayerPtr_CompareId
{
FORCEINLINE bool operator()(const FPassthroughLayerPtr& A, const FPassthroughLayerPtr& B) const
{
return A->GetDesc().GetLayerId() < B->GetDesc().GetLayerId();
}
};
struct FLayerDesc_ComparePriority
{
FORCEINLINE int32 GetLayerTypePriority(const IStereoLayers::FLayerDesc& LayerDesc) const
{
const bool IsPokeAHole = ((LayerDesc.Flags & IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH) != 0) && LayerDesc.HasShape<FUserDefinedLayer>();
bool IsUnderlay = false;
if (LayerDesc.HasShape<FReconstructedLayer>())
{
const FReconstructedLayer& ReconstructedLayerProps = LayerDesc.GetShape<FReconstructedLayer>();
IsUnderlay = (ReconstructedLayerProps.PassthroughLayerOrder == PassthroughLayerOrder_Underlay);
}
else if (LayerDesc.HasShape<FUserDefinedLayer>())
{
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
IsUnderlay = (UserDefinedLayerProps.PassthroughLayerOrder == PassthroughLayerOrder_Underlay);
}
const int32 Priority = IsUnderlay ? -2 : IsPokeAHole ? -1
: 1;
return Priority;
}
FORCEINLINE bool operator()(const IStereoLayers::FLayerDesc& A, const IStereoLayers::FLayerDesc& B) const
{
// First order layers by type
const int32 PassA = GetLayerTypePriority(A);
const int32 PassB = GetLayerTypePriority(B);
if (PassA != PassB)
{
return PassA < PassB;
}
// Draw layers by ascending priority
if (A.Priority != B.Priority)
{
return A.Priority < B.Priority;
}
// Draw layers by ascending id
return A.Id < B.Id;
}
FORCEINLINE bool operator()(const FPassthroughLayerPtr& A, const FPassthroughLayerPtr& B) const
{
return (*this)(A->GetDesc(), B->GetDesc());
}
};
} // namespace XRPassthrough

View File

@@ -0,0 +1,728 @@
// @lint-ignore-every LICENSELINT
// Copyright 1998-2020 Epic Games, Inc. All Rights Reserved.
#include "OculusXRPassthroughLayerComponent.h"
#include "Engine/StaticMesh.h"
#include "Engine/GameEngine.h"
#include "Components/StaticMeshComponent.h"
#include "ProceduralMeshComponent.h"
#include "OculusXRHMD.h"
#include "OculusXRPassthroughLayerShapes.h"
#include "OculusXRPersistentPassthroughInstance.h"
#include "OculusXRPassthroughSubsystem.h"
#include "Curves/CurveLinearColor.h"
#include "StaticMeshResources.h"
DEFINE_LOG_CATEGORY(LogOculusPassthrough);
void UOculusXRStereoLayerShapeReconstructed::ApplyShape(IStereoLayers::FLayerDesc& LayerDesc)
{
const FEdgeStyleParameters EdgeStyleParameters(
bEnableEdgeColor,
bEnableColorMap,
TextureOpacityFactor,
Brightness,
Contrast,
Posterize,
Saturation,
EdgeColor,
ColorScale,
ColorOffset,
ColorMapType,
GetColorArray(bUseColorMapCurve, ColorMapCurve),
GenerateColorLutDescription(LutWeight, ColorLUTSource, ColorLUTTarget));
LayerDesc.SetShape<FReconstructedLayer>(EdgeStyleParameters, LayerOrder);
}
void UOculusXRStereoLayerShapeUserDefined::ApplyShape(IStereoLayers::FLayerDesc& LayerDesc)
{
// If there is no user geometry, set the layer hidden to avoid unnecessary cost
if (UserGeometryList.IsEmpty())
LayerDesc.Flags |= IStereoLayers::LAYER_FLAG_HIDDEN;
const FEdgeStyleParameters EdgeStyleParameters(
bEnableEdgeColor,
bEnableColorMap,
TextureOpacityFactor,
Brightness,
Contrast,
Posterize,
Saturation,
EdgeColor,
ColorScale,
ColorOffset,
ColorMapType,
GetColorArray(bUseColorMapCurve, ColorMapCurve),
GenerateColorLutDescription(LutWeight, ColorLUTSource, ColorLUTTarget));
LayerDesc.SetShape<FUserDefinedLayer>(UserGeometryList, EdgeStyleParameters, LayerOrder);
}
void UOculusXRStereoLayerShapeUserDefined::AddGeometry(const FString& MeshName, OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FTransform Transform, bool bUpdateTransform)
{
FUserDefinedGeometryDesc UserDefinedGeometryDesc(
MeshName,
PassthroughMesh,
Transform,
bUpdateTransform);
UserGeometryList.Add(UserDefinedGeometryDesc);
}
void UOculusXRStereoLayerShapeUserDefined::RemoveGeometry(const FString& MeshName)
{
UserGeometryList.RemoveAll([MeshName](const FUserDefinedGeometryDesc& Desc) {
return Desc.MeshName == MeshName;
});
}
UOculusXRPassthroughLayerComponent::UOculusXRPassthroughLayerComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UOculusXRPassthroughLayerComponent::DestroyComponent(bool bPromoteChildren)
{
Super::DestroyComponent(bPromoteChildren);
UOculusXRPassthroughSubsystem* ptSubsystem = UOculusXRPassthroughSubsystem::GetPassthroughSubsystem(GetWorld());
if (ptSubsystem != nullptr)
{
ptSubsystem->OnAnyLayerResumed.RemoveDynamic(this, &UOculusXRPassthroughLayerComponent::OnAnyLayerResumedEvent);
}
#ifdef WITH_OCULUS_BRANCH
IStereoLayers* StereoLayers;
if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr)
{
StereoLayers->DestroyLayer(LayerId);
LayerId = 0;
}
#endif
}
void UOculusXRPassthroughLayerComponent::OnRegister()
{
Super::OnRegister();
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerComponent::BeginPlay()
{
UOculusXRPassthroughSubsystem* ptSubsystem = UOculusXRPassthroughSubsystem::GetPassthroughSubsystem(GetWorld());
if (ptSubsystem != nullptr)
{
ptSubsystem->OnAnyLayerResumed.AddDynamic(this, &UOculusXRPassthroughLayerComponent::OnAnyLayerResumedEvent);
}
Super::BeginPlay();
}
void UOculusXRPassthroughLayerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
if (Texture == nullptr && !LayerRequiresTexture())
{
// UStereoLayerComponent hides components without textures
Texture = GEngine->DefaultTexture;
}
UpdatePassthroughObjects();
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
void UOculusXRPassthroughLayerComponent::UpdatePassthroughObjects()
{
UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape);
if (UserShape)
{
bool bDirty = false;
for (FUserDefinedGeometryDesc& Entry : UserShape->GetUserGeometryList())
{
if (Entry.bUpdateTransform)
{
const UMeshComponent** MeshComponent = PassthroughComponentMap.Find(Entry.MeshName);
if (MeshComponent)
{
Entry.Transform = (*MeshComponent)->GetComponentTransform();
bDirty = true;
}
}
}
if (bDirty)
{
MarkStereoLayerDirty();
}
}
}
OculusXRHMD::FOculusPassthroughMeshRef UOculusXRPassthroughLayerComponent::CreatePassthroughMesh(UProceduralMeshComponent* ProceduralMeshComponent)
{
if (!ProceduralMeshComponent)
{
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Procedural Mesh is nullptr"));
return nullptr;
}
TArray<int32> Triangles;
TArray<FVector> Vertices;
int32 NumSections = ProceduralMeshComponent->GetNumSections();
int VertexOffset = 0; // Each section start with vertex IDs of 0, in order to create a single mesh from all sections we need to offset those IDs by the amount of previous vertices
for (int32 s = 0; s < NumSections; ++s)
{
FProcMeshSection* ProcMeshSection = ProceduralMeshComponent->GetProcMeshSection(s);
for (int32 i = 0; i < ProcMeshSection->ProcIndexBuffer.Num(); ++i)
{
Triangles.Add(VertexOffset + ProcMeshSection->ProcIndexBuffer[i]);
}
for (int32 i = 0; i < ProcMeshSection->ProcVertexBuffer.Num(); ++i)
{
Vertices.Add(ProcMeshSection->ProcVertexBuffer[i].Position);
}
VertexOffset += ProcMeshSection->ProcVertexBuffer.Num();
}
OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = new OculusXRHMD::FOculusPassthroughMesh(Vertices, Triangles);
return PassthroughMesh;
}
OculusXRHMD::FOculusPassthroughMeshRef UOculusXRPassthroughLayerComponent::CreatePassthroughMesh(UStaticMeshComponent* StaticMeshComponent)
{
if (!StaticMeshComponent)
{
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh is nullptr"));
return nullptr;
}
UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
if (!Mesh || !Mesh->GetRenderData())
{
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh has no Renderdata"));
return nullptr;
}
if (Mesh->GetNumLODs() == 0)
{
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh has no LODs"));
return nullptr;
}
if (!Mesh->bAllowCPUAccess)
{
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh Requires CPU Access"));
return nullptr;
}
const int32 LODIndex = 0;
FStaticMeshLODResources& LOD = Mesh->GetRenderData()->LODResources[LODIndex];
TArray<int32> Triangles;
const int32 NumIndices = LOD.IndexBuffer.GetNumIndices();
for (int32 i = 0; i < NumIndices; ++i)
{
Triangles.Add(LOD.IndexBuffer.GetIndex(i));
}
TArray<FVector> Vertices;
const int32 NumVertices = LOD.VertexBuffers.PositionVertexBuffer.GetNumVertices();
for (int32 i = 0; i < NumVertices; ++i)
{
Vertices.Add((FVector)LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(i));
}
OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = new OculusXRHMD::FOculusPassthroughMesh(Vertices, Triangles);
return PassthroughMesh;
}
void UOculusXRPassthroughLayerComponent::AddSurfaceGeometry(AStaticMeshActor* StaticMeshActor, bool updateTransform)
{
if (StaticMeshActor)
{
UStaticMeshComponent* StaticMeshComponent = StaticMeshActor->GetStaticMeshComponent();
if (StaticMeshComponent)
AddStaticSurfaceGeometry(StaticMeshComponent, updateTransform);
}
}
void UOculusXRPassthroughLayerComponent::AddStaticSurfaceGeometry(UStaticMeshComponent* StaticMeshComponent, bool updateTransform)
{
if (!StaticMeshComponent)
return;
UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape);
if (!UserShape)
return;
OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = CreatePassthroughMesh(StaticMeshComponent);
if (!PassthroughMesh)
return;
const FString MeshName = StaticMeshComponent->GetFullName();
const FTransform Transform = StaticMeshComponent->GetComponentTransform();
UserShape->AddGeometry(MeshName, PassthroughMesh, Transform, updateTransform);
PassthroughComponentMap.Add(MeshName, StaticMeshComponent);
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerComponent::AddProceduralSurfaceGeometry(UProceduralMeshComponent* ProceduralMeshComponent, bool updateTransform)
{
if (!ProceduralMeshComponent)
return;
UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape);
if (!UserShape)
return;
OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = CreatePassthroughMesh(ProceduralMeshComponent);
if (!PassthroughMesh)
return;
const FString MeshName = ProceduralMeshComponent->GetFullName();
const FTransform Transform = ProceduralMeshComponent->GetComponentTransform();
UserShape->AddGeometry(MeshName, PassthroughMesh, Transform, updateTransform);
PassthroughComponentMap.Add(MeshName, ProceduralMeshComponent);
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerComponent::RemoveSurfaceGeometry(AStaticMeshActor* StaticMeshActor)
{
if (StaticMeshActor)
RemoveSurfaceGeometryComponent(StaticMeshActor->GetStaticMeshComponent());
}
void UOculusXRPassthroughLayerComponent::RemoveStaticSurfaceGeometry(UStaticMeshComponent* StaticMeshComponent)
{
RemoveSurfaceGeometryComponent(StaticMeshComponent);
}
void UOculusXRPassthroughLayerComponent::RemoveProceduralSurfaceGeometry(UProceduralMeshComponent* ProceduralMeshComponent)
{
RemoveSurfaceGeometryComponent(ProceduralMeshComponent);
}
void UOculusXRPassthroughLayerComponent::RemoveSurfaceGeometryComponent(UMeshComponent* MeshComponent)
{
if (!MeshComponent)
return;
UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape);
if (!UserShape)
return;
const FString MeshName = MeshComponent->GetFullName();
UserShape->RemoveGeometry(MeshName);
PassthroughComponentMap.Remove(MeshName);
MarkStereoLayerDirty();
}
bool UOculusXRPassthroughLayerComponent::IsSurfaceGeometry(AStaticMeshActor* StaticMeshActor) const
{
return StaticMeshActor ? IsSurfaceGeometryComponent(StaticMeshActor->GetStaticMeshComponent()) : false;
}
bool UOculusXRPassthroughLayerComponent::IsSurfaceGeometryComponent(const UMeshComponent* MeshComponent) const
{
return MeshComponent ? PassthroughComponentMap.Contains(MeshComponent->GetFullName()) : false;
}
void UOculusXRPassthroughLayerComponent::MarkPassthroughStyleForUpdate()
{
bPassthroughStyleNeedsUpdate = true;
}
#if WITH_EDITOR
bool UOculusXRPassthroughLayerComponent::CanEditChange(const FProperty* InProperty) const
{
if (!Super::CanEditChange(InProperty))
return false;
const FName PropertyName = InProperty->GetFName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, Texture)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, LeftTexture)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bLiveTexture)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bNoAlphaChannel)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bQuadPreserveTextureRatio)
#if defined(WITH_OCULUS_BRANCH) && UE_VERSION_OLDER_THAN(5, 5, 0)
// clang-format off
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bBicubicFiltering)
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// clang-format on
#endif
#if !UE_VERSION_OLDER_THAN(5, 4, 0)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, AdditionalFlags)
#endif
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, QuadSize)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, UVRect)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, StereoLayerType))
{
return false;
}
return true;
}
#endif // WITH_EDITOR
void UOculusXRPassthroughLayerComponent::OnAnyLayerResumedEvent(int InLayerId)
{
if (LayerId == InLayerId)
{
OnLayerResumed.Broadcast();
}
}
bool UOculusXRPassthroughLayerComponent::LayerRequiresTexture()
{
const bool bIsPassthroughShape = Shape && (Shape->IsA<UOculusXRStereoLayerShapeReconstructed>() || Shape->IsA<UOculusXRStereoLayerShapeUserDefined>());
return !bIsPassthroughShape;
}
void UOculusXRPassthroughLayerBase::SetTextureOpacity(float InOpacity)
{
if (TextureOpacityFactor == InOpacity)
{
return;
}
TextureOpacityFactor = InOpacity;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::EnableEdgeColor(bool bInEnableEdgeColor)
{
if (bEnableEdgeColor == bInEnableEdgeColor)
{
return;
}
bEnableEdgeColor = bInEnableEdgeColor;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::EnableColorMap(bool bInEnableColorMap)
{
if (bEnableColorMap == bInEnableColorMap)
{
return;
}
bEnableColorMap = bInEnableColorMap;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetEdgeRenderingColor(FLinearColor InEdgeColor)
{
if (EdgeColor == InEdgeColor)
{
return;
}
EdgeColor = InEdgeColor;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::EnableColorMapCurve(bool bInEnableColorMapCurve)
{
if (bUseColorMapCurve == bInEnableColorMapCurve)
{
return;
}
bUseColorMapCurve = bInEnableColorMapCurve;
ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve);
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetColorMapCurve(UCurveLinearColor* InColorMapCurve)
{
if (ColorMapCurve == InColorMapCurve)
{
return;
}
ColorMapCurve = InColorMapCurve;
ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve);
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetColorMapType(EOculusXRColorMapType InColorMapType)
{
if (ColorMapType == InColorMapType)
{
return;
}
ColorMapType = InColorMapType;
ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve);
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetColorArray(const TArray<FLinearColor>& InColorArray)
{
if (InColorArray.Num() == 0)
{
return;
}
if (ColorMapType != ColorMapType_GrayscaleToColor)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorArray is ignored for color map types other than Grayscale to Color."));
return;
}
if (bUseColorMapCurve)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("UseColorMapCurve is enabled on the layer. Automatic disable and use the Array for color lookup"));
}
bUseColorMapCurve = false;
ColorArray = InColorArray;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::ClearColorMap()
{
ColorArray.Empty();
}
void UOculusXRPassthroughLayerBase::SetColorMapControls(float InContrast, float InBrightness, float InPosterize)
{
if (ColorMapType != ColorMapType_Grayscale && ColorMapType != ColorMapType_GrayscaleToColor)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorMapControls is ignored for color map types other than Grayscale and Grayscale to color."));
return;
}
Contrast = FMath::Clamp(InContrast, -1.0f, 1.0f);
Brightness = FMath::Clamp(InBrightness, -1.0f, 1.0f);
Posterize = FMath::Clamp(InPosterize, 0.0f, 1.0f);
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetBrightnessContrastSaturation(float InContrast, float InBrightness, float InSaturation)
{
if (ColorMapType != ColorMapType_ColorAdjustment)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetBrightnessContrastSaturation is ignored for color map types other than Color Adjustment."));
return;
}
Contrast = FMath::Clamp(InContrast, -1.0f, 1.0f);
Brightness = FMath::Clamp(InBrightness, -1.0f, 1.0f);
Saturation = FMath::Clamp(InSaturation, -1.0f, 1.0f);
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetColorScaleAndOffset(FLinearColor InColorScale, FLinearColor InColorOffset)
{
if (ColorScale == InColorScale && ColorOffset == InColorOffset)
{
return;
}
ColorScale = InColorScale;
ColorOffset = InColorOffset;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetLayerPlacement(EOculusXRPassthroughLayerOrder InLayerOrder)
{
if (LayerOrder == InLayerOrder)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Same layer order as before, no change needed"));
return;
}
LayerOrder = InLayerOrder;
this->MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetColorLUTSource(class UOculusXRPassthroughColorLut* InColorLUTSource)
{
if (ColorMapType != ColorMapType_ColorLut && ColorMapType != ColorMapType_ColorLut_Interpolated)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorLUT is ignored for color map types other than Color LUT."));
return;
}
if (InColorLUTSource == ColorLUTSource)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Same color LUT source as before, no change needed"));
return;
}
// Remove reference from the old LUT
if (ColorLUTSource)
ColorLUTSource->RemoveReference(this);
ColorLUTSource = InColorLUTSource;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetColorLUTTarget(class UOculusXRPassthroughColorLut* InColorLUTTarget)
{
if (ColorMapType != ColorMapType_ColorLut_Interpolated)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorLUTTarget is ignored for color map types other than Interpolated Color LUT."));
return;
}
if (InColorLUTTarget == ColorLUTTarget)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Same color LUT source as before, no change needed"));
return;
}
// Remove reference from the old LUT
if (ColorLUTTarget)
ColorLUTTarget->RemoveReference(this);
ColorLUTTarget = InColorLUTTarget;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::SetColorLUTWeight(float InWeight)
{
if (ColorMapType != ColorMapType_ColorLut && ColorMapType != ColorMapType_ColorLut_Interpolated)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetWeight is ignored for color map types other than Color LUT."));
return;
}
if (LutWeight == InWeight)
{
UE_LOG(LogOculusPassthrough, Warning, TEXT("Same lut weight as before, no change needed"));
return;
}
LutWeight = InWeight;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::RemoveColorLut()
{
ClearLUTsReferences();
ColorLUTSource = nullptr;
ColorLUTTarget = nullptr;
MarkStereoLayerDirty();
}
void UOculusXRPassthroughLayerBase::ClearLUTsReferences()
{
// Clear lut references
if (ColorLUTSource)
ColorLUTSource->RemoveReference(this);
if (ColorLUTTarget)
ColorLUTTarget->RemoveReference(this);
}
void UOculusXRPassthroughLayerBase::BeginDestroy()
{
ClearLUTsReferences();
Super::BeginDestroy();
}
TArray<FLinearColor> UOculusXRPassthroughLayerBase::GenerateColorArrayFromColorCurve(const UCurveLinearColor* InColorMapCurve) const
{
if (InColorMapCurve == nullptr)
{
return TArray<FLinearColor>();
}
TArray<FLinearColor> NewColorArray;
constexpr uint32 TotalEntries = 256;
NewColorArray.Empty();
NewColorArray.SetNum(TotalEntries);
for (int32 Index = 0; Index < TotalEntries; ++Index)
{
const float Alpha = ((float)Index / TotalEntries);
NewColorArray[Index] = InColorMapCurve->GetLinearColorValue(Alpha);
}
return NewColorArray;
}
TArray<FLinearColor> UOculusXRPassthroughLayerBase::GetOrGenerateNeutralColorArray()
{
if (NeutralColorArray.Num() == 0)
{
const uint32 TotalEntries = 256;
NeutralColorArray.SetNum(TotalEntries);
for (int32 Index = 0; Index < TotalEntries; ++Index)
{
NeutralColorArray[Index] = FLinearColor((float)Index / TotalEntries, (float)Index / TotalEntries, (float)Index / TotalEntries);
}
}
return NeutralColorArray;
}
TArray<FLinearColor> UOculusXRPassthroughLayerBase::GenerateColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve)
{
TArray<FLinearColor> NewColorArray;
if (bInUseColorMapCurve)
{
NewColorArray = GenerateColorArrayFromColorCurve(InColorMapCurve);
}
// Check for existing Array, otherwise generate a neutral one
if (NewColorArray.Num() == 0)
{
NewColorArray = GetOrGenerateNeutralColorArray();
}
return NewColorArray;
}
TArray<FLinearColor> UOculusXRPassthroughLayerBase::GetColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve)
{
if (ColorArray.Num() == 0)
{
if (bInUseColorMapCurve)
{
return GenerateColorArray(bInUseColorMapCurve, InColorMapCurve);
}
return GetOrGenerateNeutralColorArray();
}
return ColorArray;
}
FColorLutDesc UOculusXRPassthroughLayerBase::GenerateColorLutDescription(float InLutWeight, UOculusXRPassthroughColorLut* InLutSource, UOculusXRPassthroughColorLut* InLutTarget)
{
TArray<uint64> ColorLuts;
if (InLutSource != nullptr && InLutSource->ColorLutType != EColorLutType::None)
{
uint64 ColorLutHandle = InLutSource->GetHandle(this);
if (ColorLutHandle != 0)
{
ColorLuts.Add(ColorLutHandle);
}
}
if (InLutTarget != nullptr && ColorMapType == EOculusXRColorMapType::ColorMapType_ColorLut_Interpolated && ColorLuts.Num() > 0 && InLutSource->ColorLutType != EColorLutType::None)
{
uint64 ColorLutHandle = InLutTarget->GetHandle(this);
if (ColorLutHandle != 0)
{
ColorLuts.Add(ColorLutHandle);
}
}
return FColorLutDesc(ColorLuts, InLutWeight);
}
void UOculusXRPassthroughLayerBase::MarkStereoLayerDirty()
{
if (UStereoLayerComponent* stereoLayerComponent = Cast<UStereoLayerComponent>(GetOuter()))
{
stereoLayerComponent->MarkStereoLayerDirty();
}
else if (UOculusXRPersistentPassthroughInstance* pptInstance = Cast<UOculusXRPersistentPassthroughInstance>(GetOuter()))
{
pptInstance->UpdateLayer();
}
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRPassthroughModule.h"
#include "OculusXRHMD.h"
#include "OculusXRPassthroughEventHandling.h"
#include "Misc/CoreDelegates.h"
DEFINE_LOG_CATEGORY(LogOculusXRPassthrough);
#define LOCTEXT_NAMESPACE "OculusXRPassthrough"
//-------------------------------------------------------------------------------------------------
// FOculusXRPassthroughModule
//-------------------------------------------------------------------------------------------------
FOculusXRPassthroughModule::FOculusXRPassthroughModule()
{
}
void FOculusXRPassthroughModule::StartupModule()
{
PassthroughXR = MakeShareable(new XRPassthrough::FPassthroughXR());
PassthroughXR->RegisterAsOpenXRExtension();
FCoreDelegates::OnPostEngineInit.AddRaw(this, &FOculusXRPassthroughModule::OnPostEngineInit);
}
void FOculusXRPassthroughModule::ShutdownModule()
{
}
void FOculusXRPassthroughModule::OnPostEngineInit()
{
if (IsRunningCommandlet())
{
return;
}
if (!GEngine)
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("No GEngine, cannot add event polling delegate."));
return;
}
OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
if (!HMD)
{
FName XRSystemName = GEngine->XRSystem ? GEngine->XRSystem->GetSystemName() : "None";
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot add event polling delegate. (current xrsystem : %s)"), *XRSystemName.ToString());
return;
}
HMD->AddEventPollingDelegate(OculusXRHMD::FOculusXRHMDEventPollingDelegate::CreateStatic(&OculusXRPassthrough::FOculusXRPassthroughEventHandling::OnPollEvent));
}
IMPLEMENT_MODULE(FOculusXRPassthroughModule, OculusXRPassthrough)
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,41 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "IOculusXRPassthroughModule.h"
#include "OculusXRPassthroughXR.h"
#define LOCTEXT_NAMESPACE "OculusXRPassthrough"
DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRPassthrough, Log, All);
//-------------------------------------------------------------------------------------------------
// FOculusXRPassthroughModule
//-------------------------------------------------------------------------------------------------
class FOculusXRPassthroughModule : public IOculusXRPassthroughModule
{
public:
FOculusXRPassthroughModule();
static inline FOculusXRPassthroughModule& Get()
{
return FModuleManager::LoadModuleChecked<FOculusXRPassthroughModule>("OculusXRPassthrough");
}
virtual void StartupModule() override;
virtual void ShutdownModule() override;
void OnPostEngineInit();
TWeakPtr<XRPassthrough::FPassthroughXR, ESPMode::ThreadSafe> GetPassthroughExtensionPlugin()
{
return PassthroughXR;
}
private:
typedef TSharedPtr<XRPassthrough::FPassthroughXR, ESPMode::ThreadSafe> FPassthroughXRPtr;
FPassthroughXRPtr PassthroughXR;
};
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,84 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRPassthroughSubsystem.h"
#include "Engine/World.h"
#include "Engine/GameInstance.h"
#include "OculusXRPersistentPassthroughInstance.h"
#include "OculusXRHMDRuntimeSettings.h"
#include "OculusXRHMD.h"
#include "OculusXRPassthroughEventHandling.h"
#include "OculusXRPassthroughModule.h"
#include "Engine/World.h"
#include "Engine/Texture2D.h"
#include "Rendering/Texture2DResource.h"
UOculusXRPassthroughSubsystem* UOculusXRPassthroughSubsystem::GetPassthroughSubsystem(const UWorld* InWorld)
{
if (InWorld)
{
return UGameInstance::GetSubsystem<UOculusXRPassthroughSubsystem>(InWorld->GetGameInstance());
}
return nullptr;
}
UOculusXRPassthroughSubsystem::UOculusXRPassthroughSubsystem()
{
}
bool UOculusXRPassthroughSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
// return true if "Passthrough Enabled" is checked in MetaXR plugin's settings.
return GetDefault<UOculusXRHMDRuntimeSettings>()->bInsightPassthroughEnabled;
}
void UOculusXRPassthroughSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
DelegateHandleLayerResumed = OculusXRPassthrough::FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed.AddLambda([this](int layerID) {
UE_LOG(LogOculusXRPassthrough, Log, TEXT("UOculusXRPassthroughSubsystem - Received layer #%d resumed event"), layerID);
OnAnyLayerResumed.Broadcast(layerID);
});
}
void UOculusXRPassthroughSubsystem::Deinitialize()
{
OculusXRPassthrough::FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed.Remove(DelegateHandleLayerResumed);
}
UOculusXRPersistentPassthroughInstance* UOculusXRPassthroughSubsystem::InitializePersistentPassthrough(FOculusXRPersistentPassthroughParameters Parameters, const FOculusXRPassthrough_LayerResumed_Single& LayerResumed)
{
if (IsValid(PPTInstance))
{
PPTInstance->AddLayerResumedSingleDelegate(LayerResumed);
PPTInstance->UpdateParameters(Parameters);
return PPTInstance;
}
PPTInstance = NewObject<UOculusXRPersistentPassthroughInstance>(this, "PersistentPassthroughInstance", RF_NoFlags);
PPTInstance->AddLayerResumedSingleDelegate(LayerResumed);
PPTInstance->InitLayer(Parameters);
OnAnyLayerResumed.AddDynamic(PPTInstance, &UOculusXRPersistentPassthroughInstance::OnAnyLayerResumedEvent);
return PPTInstance;
}
void UOculusXRPassthroughSubsystem::DestroyPersistentPassthrough()
{
if (!IsValid(PPTInstance))
{
return;
}
OnAnyLayerResumed.RemoveDynamic(PPTInstance, &UOculusXRPersistentPassthroughInstance::OnAnyLayerResumedEvent);
PPTInstance->ConditionalBeginDestroy();
PPTInstance = nullptr;
}
UOculusXRPersistentPassthroughInstance* UOculusXRPassthroughSubsystem::GetPersistentPassthrough() const
{
return PPTInstance;
}

View File

@@ -0,0 +1,581 @@
// 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

View File

@@ -0,0 +1,122 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
// Including the khronos openxr header here to override the one included from IOpenXREntensionPlugin, making sure we use the latest one.
#include "khronos/openxr/openxr.h"
#include "khronos/openxr/meta_openxr_preview/meta_passthrough_layer_resumed_event.h"
#include "IOpenXRExtensionPlugin.h"
#include "IStereoLayers.h"
#include "OculusXRPassthroughLayer.h"
#include "OculusXRPassthroughXR_DeletionQueue.h"
#define LOCTEXT_NAMESPACE "OculusXRPassthrough"
class FOpenXRSwapchain;
class FOpenXRHMD;
class UMaterial;
namespace XRPassthrough
{
struct FSettings
{
UMaterial* PokeAHoleMaterial;
bool bPassthroughEnabled;
bool bExtLayerAlphaBlendAvailable;
bool bExtPassthroughAvailable;
bool bExtTriangleMeshAvailable;
bool bExtColorLutAvailable;
bool bExtLayerResumedEventAvailable;
};
typedef TSharedPtr<FSettings, ESPMode::ThreadSafe> FSettingsPtr;
class FPassthroughXR : public IOpenXRExtensionPlugin
{
public:
// IOculusXROpenXRHMDPlugin
virtual void* OnWaitFrame(XrSession InSession, void* InNext) override;
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override;
virtual void PostCreateSession(XrSession InSession) override;
virtual void OnDestroySession(XrSession InSession) override;
virtual const void* OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags) override;
#ifdef WITH_OCULUS_BRANCH
virtual void OnCreateLayer(const IStereoLayers::FLayerDesc& InLayerDesc, uint32 LayerId) override;
virtual void OnDestroyLayer(uint32 LayerId) override;
virtual void OnSetLayerDesc(uint32 LayerId, const IStereoLayers::FLayerDesc& InLayerDesc) override;
#endif
virtual void OnSetupLayers_RenderThread(XrSession InSession, const TArray<uint32_t>& LayerIds) override;
virtual void UpdateCompositionLayers(XrSession InSession, TArray<XrCompositionLayerBaseHeaderType*>& Headers) override;
#ifdef WITH_OCULUS_BRANCH
virtual bool OnEndGameFrame(FWorldContext& WorldContext) override;
#endif
virtual void OnBeginRendering_GameThread(XrSession InSession) override;
#ifdef WITH_OCULUS_BRANCH
virtual void OnBeginRenderingLate_RenderThread(XrSession InSession, FRHICommandListImmediate& RHICmdList) override;
virtual void FinishRenderFrame_RenderThread(FRDGBuilder& GraphBuilder) override;
#endif
virtual void OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader) override;
public:
static TWeakPtr<FPassthroughXR> GetInstance();
static bool IsPassthoughLayerDesc(const IStereoLayers::FLayerDesc& LayerDesc);
FPassthroughXR();
virtual ~FPassthroughXR();
void RegisterAsOpenXRExtension();
XrPassthroughFB GetPassthroughInstance() const
{
return PassthroughInstance;
}
FSettingsPtr GetSettings() const
{
return Settings;
}
OCULUSXRPASSTHROUGH_API bool IsPassthroughEnabled(void) const;
private:
void InvertTextureAlpha_RenderThread(FRHICommandList& RHICmdList, FRHITexture* Texture, FRHITexture* TempTexture, const FIntRect& ViewportRect);
FPassthroughLayerPtr CreateStereoLayerFromDesc(const IStereoLayers::FLayerDesc& LayerDesc) const;
void ShutdownPassthrough(XrSession InSession);
void InitializePassthrough(XrSession InSession);
void Update_GameThread(XrSession InSession);
IRendererModule* RendererModule;
FTextureRHIRef InvAlphaTexture;
FOpenXRSwapchain* ColorSwapchain;
FRHITexture* ColorSwapChainTexture;
XrCompositionLayerAlphaBlendFB ProjectionLayerAlphaBlend;
TMap<uint32, FPassthroughLayerPtr> LayerMap;
TArray<FPassthroughLayerPtr> Layers_RenderThread;
XRPassthrough::FDeferredDeletionQueue DeferredDeletion;
bool bPassthroughInitialized;
XrPassthroughFB PassthroughInstance;
FSettingsPtr Settings;
FOpenXRHMD* OpenXRHMD;
float WorldToMetersScale;
float WorldToMetersScale_RenderThread;
FTransform TrackingToWorld;
FTransform TrackingToWorld_RenderThread;
};
} // namespace XRPassthrough
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,48 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRPassthroughXRFunctions.h"
#include "OpenXRCore.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
namespace XRPassthrough
{
PFN_xrCreatePassthroughFB xrCreatePassthroughFB = nullptr;
PFN_xrDestroyPassthroughFB xrDestroyPassthroughFB = nullptr;
PFN_xrPassthroughStartFB xrPassthroughStartFB = nullptr;
PFN_xrPassthroughPauseFB xrPassthroughPauseFB = nullptr;
PFN_xrCreatePassthroughLayerFB xrCreatePassthroughLayerFB = nullptr;
PFN_xrDestroyPassthroughLayerFB xrDestroyPassthroughLayerFB = nullptr;
PFN_xrPassthroughLayerPauseFB xrPassthroughLayerPauseFB = nullptr;
PFN_xrPassthroughLayerResumeFB xrPassthroughLayerResumeFB = nullptr;
PFN_xrPassthroughLayerSetStyleFB xrPassthroughLayerSetStyleFB = nullptr;
TOptional<PFN_xrCreateTriangleMeshFB> xrCreateTriangleMeshFB = nullptr;
TOptional<PFN_xrDestroyTriangleMeshFB> xrDestroyTriangleMeshFB = nullptr;
PFN_xrCreateGeometryInstanceFB xrCreateGeometryInstanceFB = nullptr;
PFN_xrDestroyGeometryInstanceFB xrDestroyGeometryInstanceFB = nullptr;
PFN_xrGeometryInstanceSetTransformFB xrGeometryInstanceSetTransformFB = nullptr;
PFN_xrCreatePassthroughColorLutMETA xrCreatePassthroughColorLutMETA = nullptr;
PFN_xrDestroyPassthroughColorLutMETA xrDestroyPassthroughColorLutMETA = nullptr;
PFN_xrUpdatePassthroughColorLutMETA xrUpdatePassthroughColorLutMETA = nullptr;
void InitOpenXRFunctions(XrInstance InInstance)
{
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreatePassthroughFB", &xrCreatePassthroughFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyPassthroughFB", &xrDestroyPassthroughFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughStartFB", &xrPassthroughStartFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughPauseFB", &xrPassthroughPauseFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreatePassthroughLayerFB", &xrCreatePassthroughLayerFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyPassthroughLayerFB", &xrDestroyPassthroughLayerFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughLayerPauseFB", &xrPassthroughLayerPauseFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughLayerResumeFB", &xrPassthroughLayerResumeFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughLayerSetStyleFB", &xrPassthroughLayerSetStyleFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreateTriangleMeshFB", &xrCreateTriangleMeshFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyTriangleMeshFB", &xrDestroyTriangleMeshFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreateGeometryInstanceFB", &xrCreateGeometryInstanceFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyGeometryInstanceFB", &xrDestroyGeometryInstanceFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGeometryInstanceSetTransformFB", &xrGeometryInstanceSetTransformFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreatePassthroughColorLutMETA", &xrCreatePassthroughColorLutMETA);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyPassthroughColorLutMETA", &xrDestroyPassthroughColorLutMETA);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrUpdatePassthroughColorLutMETA", &xrUpdatePassthroughColorLutMETA);
}
} // namespace XRPassthrough

View File

@@ -0,0 +1,29 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
#include "Misc/Optional.h"
namespace XRPassthrough
{
extern PFN_xrCreatePassthroughFB xrCreatePassthroughFB;
extern PFN_xrDestroyPassthroughFB xrDestroyPassthroughFB;
extern PFN_xrPassthroughStartFB xrPassthroughStartFB;
extern PFN_xrPassthroughPauseFB xrPassthroughPauseFB;
extern PFN_xrCreatePassthroughLayerFB xrCreatePassthroughLayerFB;
extern PFN_xrDestroyPassthroughLayerFB xrDestroyPassthroughLayerFB;
extern PFN_xrPassthroughLayerPauseFB xrPassthroughLayerPauseFB;
extern PFN_xrPassthroughLayerResumeFB xrPassthroughLayerResumeFB;
extern PFN_xrPassthroughLayerSetStyleFB xrPassthroughLayerSetStyleFB;
extern TOptional<PFN_xrCreateTriangleMeshFB> xrCreateTriangleMeshFB;
extern TOptional<PFN_xrDestroyTriangleMeshFB> xrDestroyTriangleMeshFB;
extern PFN_xrCreateGeometryInstanceFB xrCreateGeometryInstanceFB;
extern PFN_xrDestroyGeometryInstanceFB xrDestroyGeometryInstanceFB;
extern PFN_xrGeometryInstanceSetTransformFB xrGeometryInstanceSetTransformFB;
extern PFN_xrCreatePassthroughColorLutMETA xrCreatePassthroughColorLutMETA;
extern PFN_xrDestroyPassthroughColorLutMETA xrDestroyPassthroughColorLutMETA;
extern PFN_xrUpdatePassthroughColorLutMETA xrUpdatePassthroughColorLutMETA;
void InitOpenXRFunctions(XrInstance InInstance);
} // namespace XRPassthrough

View File

@@ -0,0 +1,50 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRPassthroughXR_DeletionQueue.h"
#include "XRThreadUtils.h"
#include "OculusXRHMDModule.h"
#include "OculusXRPassthroughXRFunctions.h"
#include "OculusXRPassthroughLayer.h"
namespace XRPassthrough
{
//-------------------------------------------------------------------------------------------------
// FDeferredDeletionQueue
//-------------------------------------------------------------------------------------------------
const uint32 NUM_FRAMES_TO_WAIT_FOR_OPENXR_LAYER_DELETE = 7;
void FDeferredDeletionQueue::AddOpenXRLayerToDeferredDeletionQueue(const XRPassthrough::FPassthroughLayerPtr& ptr)
{
DeferredDeletionEntry Entry;
Entry.OpenXRLayer = ptr;
Entry.FrameEnqueued = GFrameCounter;
Entry.EntryType = DeferredDeletionEntry::DeferredDeletionEntryType::OpenXRLayer;
DeferredDeletionArray.Add(Entry);
}
void FDeferredDeletionQueue::HandleLayerDeferredDeletionQueue_RenderThread(bool bDeleteImmediately)
{
// Traverse list backwards so the swap switches to elements already tested
for (int32 Index = DeferredDeletionArray.Num() - 1; Index >= 0; --Index)
{
DeferredDeletionEntry* Entry = &DeferredDeletionArray[Index];
if (Entry->EntryType == DeferredDeletionEntry::DeferredDeletionEntryType::OpenXRLayer)
{
if (bDeleteImmediately || GFrameCounter > Entry->FrameEnqueued + NUM_FRAMES_TO_WAIT_FOR_OPENXR_LAYER_DELETE)
{
Entry->OpenXRLayer->DestroyLayer_RenderThread();
#if UE_VERSION_OLDER_THAN(5, 5, 0)
DeferredDeletionArray.RemoveAtSwap(Index, 1, false);
#else
DeferredDeletionArray.RemoveAtSwap(Index, 1, EAllowShrinking::No);
#endif
}
}
}
}
} // namespace XRPassthrough

View File

@@ -0,0 +1,42 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "OculusXRHMDPrivate.h"
namespace XRPassthrough
{
class FPassthroughLayer;
typedef TSharedPtr<XRPassthrough::FPassthroughLayer, ESPMode::ThreadSafe> FPassthroughLayerPtr;
} // namespace XRPassthrough
namespace XRPassthrough
{
//-------------------------------------------------------------------------------------------------
// FDeferredDeletionQueue
//-------------------------------------------------------------------------------------------------
class FDeferredDeletionQueue
{
public:
void AddOpenXRLayerToDeferredDeletionQueue(const FPassthroughLayerPtr& ptr);
void HandleLayerDeferredDeletionQueue_RenderThread(bool bDeleteImmediately = false);
private:
struct DeferredDeletionEntry
{
enum class DeferredDeletionEntryType
{
OpenXRLayer
};
XRPassthrough::FPassthroughLayerPtr OpenXRLayer;
uint32 FrameEnqueued;
DeferredDeletionEntryType EntryType;
};
TArray<DeferredDeletionEntry> DeferredDeletionArray;
};
} // namespace XRPassthrough

View File

@@ -0,0 +1,179 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRPersistentPassthroughInstance.h"
#include "Engine/GameEngine.h"
#include "IStereoLayers.h"
#include "OculusXRPassthroughLayerComponent.h"
#include "TextureResource.h"
void UOculusXRPersistentPassthroughInstance::SetVisible(bool InVisible)
{
Parameters.bVisible = InVisible;
UpdateLayer();
}
void UOculusXRPersistentPassthroughInstance::SetPriority(int32 InPriority)
{
Parameters.Priority = InPriority;
UpdateLayer();
}
UOculusXRPersistentPassthroughInstance::UOculusXRPersistentPassthroughInstance(const FObjectInitializer& ObjectInitializer)
{
Parameters.Shape = ObjectInitializer.CreateDefaultSubobject<UOculusXRStereoLayerShapeReconstructed>(this, TEXT("PPT_StereoLayerShapeReconstructed"));
}
void UOculusXRPersistentPassthroughInstance::InitLayer(FOculusXRPersistentPassthroughParameters InParameters)
{
UpdateParameters(InParameters);
}
void UOculusXRPersistentPassthroughInstance::UpdateParameters(FOculusXRPersistentPassthroughParameters InParameters)
{
#if WITH_EDITOR
// We need the instance to be the owner of the shape, but in editor the shape still exist and its owner is the BP calling the method.
// So we clear it to force a new one to be created with LoadShape(this)
InParameters.Shape = nullptr;
#endif
if (InParameters.LoadShape(this) == nullptr)
{
Parameters.Shape = NewObject<UOculusXRStereoLayerShapeReconstructed>(this, NAME_None, RF_Public);
}
Parameters = InParameters;
UpdateLayer();
}
void UOculusXRPersistentPassthroughInstance::UpdateLayer()
{
IStereoLayers* StereoLayers;
if (!GEngine->StereoRenderingDevice.IsValid() || (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) == nullptr)
{
return;
}
IStereoLayers::FLayerDesc LayerDesc;
LayerDesc.Priority = Parameters.Priority;
LayerDesc.QuadSize = FVector2D(100.f, 100.f);
LayerDesc.Transform = FTransform::Identity;
TObjectPtr<class UTexture2D> Texture = GEngine->DefaultTexture;
if (Texture)
{
Texture->SetForceMipLevelsToBeResident(30.0f);
LayerDesc.Texture = Texture->GetResource()->TextureRHI;
LayerDesc.Flags |= (Texture->GetMaterialType() == MCT_TextureExternal) ? IStereoLayers::LAYER_FLAG_TEX_EXTERNAL : 0;
}
LayerDesc.Flags |= (!Parameters.bVisible) ? IStereoLayers::LAYER_FLAG_HIDDEN : 0;
LayerDesc.PositionType = IStereoLayers::FaceLocked;
// Set the correct layer shape and apply any shape-specific properties
Parameters.Shape->ApplyShape(LayerDesc);
if (LayerId != IStereoLayers::FLayerDesc::INVALID_LAYER_ID)
{
StereoLayers->SetLayerDesc(LayerId, LayerDesc);
}
else
{
LayerId = StereoLayers->CreateLayer(LayerDesc);
}
StereoLayers->MarkTextureForUpdate(LayerId);
}
void UOculusXRPersistentPassthroughInstance::BeginDestroy()
{
IStereoLayers* StereoLayers;
if (LayerId != IStereoLayers::FLayerDesc::INVALID_LAYER_ID && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr)
{
StereoLayers->DestroyLayer(LayerId);
LayerId = IStereoLayers::FLayerDesc::INVALID_LAYER_ID;
}
Super::BeginDestroy();
}
void UOculusXRPersistentPassthroughInstance::OnAnyLayerResumedEvent(int InLayerId)
{
if (LayerId == InLayerId)
{
// Execute all single delegates (added from UOculusXRPassthroughSubsystem::InitializePersistentPassthrough)
for (const FOculusXRPassthrough_LayerResumed_Single& Delegate : LayerResumedSingleDelegates)
{
Delegate.ExecuteIfBound();
}
OnLayerResumed.Broadcast();
}
}
void UOculusXRPersistentPassthroughInstance::AddLayerResumedSingleDelegate(const FOculusXRPassthrough_LayerResumed_Single& Delegate)
{
if (!Delegate.IsBound())
return;
LayerResumedSingleDelegates.Add(Delegate);
}
// Save all of the Shape's properties in temporary properties which can always be serialized
void FOculusXRPersistentPassthroughParameters::ApplyShape()
{
if (!Shape)
return;
TempShape_LayerOrder = Shape->LayerOrder;
TempShape_TextureOpacityFactor = Shape->TextureOpacityFactor;
TempShape_bEnableEdgeColor = Shape->bEnableEdgeColor;
TempShape_EdgeColor = Shape->EdgeColor;
TempShape_bEnableColorMap = Shape->bEnableColorMap;
TempShape_ColorMapType = Shape->ColorMapType;
TempShape_bUseColorMapCurve = Shape->bUseColorMapCurve;
TempShape_ColorMapCurve = Shape->ColorMapCurve;
TempShape_Contrast = Shape->Contrast;
TempShape_Brightness = Shape->Brightness;
TempShape_Posterize = Shape->Posterize;
TempShape_Saturation = Shape->Saturation;
TempShape_LutWeight = Shape->LutWeight;
TempShape_ColorLUTSource = Shape->ColorLUTSource;
TempShape_ColorLUTTarget = Shape->ColorLUTTarget;
TempShape_ColorScale = Shape->ColorScale;
TempShape_ColorOffset = Shape->ColorOffset;
}
// If Shape doesn't already exists, create a new one and set it up using the temporary properties
UOculusXRStereoLayerShapeReconstructed* FOculusXRPersistentPassthroughParameters::LoadShape(UObject* owner)
{
if (Shape)
return Shape;
UOculusXRStereoLayerShapeReconstructed* NewShape = NewObject<UOculusXRStereoLayerShapeReconstructed>(owner);
NewShape->LayerOrder = TempShape_LayerOrder;
NewShape->TextureOpacityFactor = TempShape_TextureOpacityFactor;
NewShape->bEnableEdgeColor = TempShape_bEnableEdgeColor;
NewShape->EdgeColor = TempShape_EdgeColor;
NewShape->bEnableColorMap = TempShape_bEnableColorMap;
NewShape->ColorMapType = TempShape_ColorMapType;
NewShape->bUseColorMapCurve = TempShape_bUseColorMapCurve;
NewShape->ColorMapCurve = TempShape_ColorMapCurve;
NewShape->Contrast = TempShape_Contrast;
NewShape->Brightness = TempShape_Brightness;
NewShape->Posterize = TempShape_Posterize;
NewShape->Saturation = TempShape_Saturation;
NewShape->LutWeight = TempShape_LutWeight;
NewShape->ColorLUTSource = TempShape_ColorLUTSource;
NewShape->ColorLUTTarget = TempShape_ColorLUTTarget;
NewShape->ColorScale = TempShape_ColorScale;
NewShape->ColorOffset = TempShape_ColorOffset;
Shape = NewShape;
return Shape;
}