// Copyright 2023 PICO Inc. All Rights Reserved. #include "PICO_Passthrough.h" #include "PICO_PassthroughModule.h" #include "PICOOpenXRRuntimeSettings.h" #include "IXRTrackingSystem.h" #include "Engine/Engine.h" #include "OpenXRPlatformRHI.h" #include "OpenXRCore.h" #include "IOpenXRHMDModule.h" #if PLATFORM_ANDROID #include #endif //PLATFORM_ANDROID FPICOOpenXRPassthrough::FPICOOpenXRPassthrough() : Session(XR_NULL_HANDLE) , bSupportPassthroughEXT(false) { } void FPICOOpenXRPassthrough::Register() { RegisterOpenXRExtensionModularFeature(); } void FPICOOpenXRPassthrough::Unregister() { UnregisterOpenXRExtensionModularFeature(); } bool FPICOOpenXRPassthrough::GetOptionalExtensions(TArray& OutExtensions) { OutExtensions.Add("XR_FB_passthrough"); OutExtensions.Add("XR_FB_triangle_mesh"); OutExtensions.Add("XR_FB_composition_layer_alpha_blend"); return true; } void FPICOOpenXRPassthrough::PostGetSystem(XrInstance InInstance, XrSystemId InSystem) { bSupportPassthroughEXT = IOpenXRHMDModule::Get().IsExtensionEnabled(XR_FB_PASSTHROUGH_EXTENSION_NAME) && IOpenXRHMDModule::Get().IsExtensionEnabled(XR_FB_TRIANGLE_MESH_EXTENSION_NAME); if (bSupportPassthroughEXT) { XrSystemPassthroughProperties2FB passthroughSystemProperties{ XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES2_FB }; XrSystemProperties systemProperties{ XR_TYPE_SYSTEM_PROPERTIES, &passthroughSystemProperties }; xrGetSystemProperties(InInstance, InSystem, &systemProperties); bCurrentDeviceSupportedPassthrough = (passthroughSystemProperties.capabilities & XR_PASSTHROUGH_CAPABILITY_BIT_FB) == XR_PASSTHROUGH_CAPABILITY_BIT_FB; bPassthroughSupportColor = bCurrentDeviceSupportedPassthrough && (passthroughSystemProperties.capabilities & XR_PASSTHROUGH_CAPABILITY_COLOR_BIT_FB) == XR_PASSTHROUGH_CAPABILITY_COLOR_BIT_FB; bPassthroughSupportDepth = bCurrentDeviceSupportedPassthrough && (passthroughSystemProperties.capabilities & XR_PASSTHROUGH_CAPABILITY_LAYER_DEPTH_BIT_FB) == XR_PASSTHROUGH_CAPABILITY_LAYER_DEPTH_BIT_FB; UE_LOG(LogPassthroughPICO, Log, TEXT("bCurrentDeviceSupportedPassthrough:%d,bPassthroughSupportColor:%d,bPassthroughSupportDepth:%d"), bCurrentDeviceSupportedPassthrough, bPassthroughSupportColor, bPassthroughSupportDepth); if (bCurrentDeviceSupportedPassthrough) { XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrCreatePassthroughFB", (PFN_xrVoidFunction*)(&pfnXrCreatePassthroughFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrDestroyPassthroughFB", (PFN_xrVoidFunction*)(&pfnXrDestroyPassthroughFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrPassthroughStartFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughStartFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrPassthroughPauseFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughPauseFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrCreatePassthroughLayerFB", (PFN_xrVoidFunction*)(&pfnXrCreatePassthroughLayerFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrDestroyPassthroughLayerFB", (PFN_xrVoidFunction*)(&pfnDestroyPassthroughLayerFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrPassthroughLayerPauseFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughLayerPauseFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrPassthroughLayerResumeFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughLayerResumeFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrPassthroughLayerSetStyleFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughLayerSetStyleFBX))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrCreateGeometryInstanceFB", (PFN_xrVoidFunction*)(&pfnXrCreateGeometryInstanceF))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrDestroyGeometryInstanceFB", (PFN_xrVoidFunction*)(&pfnXrDestroyGeometryInstanceFB))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrGeometryInstanceSetTransformFB", (PFN_xrVoidFunction*)(&pfnXrGeometryInstanceSetTransformFB))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrCreateTriangleMeshFB", (PFN_xrVoidFunction*)(&pfnXrCreateTriangleMeshFB))); XR_ENSURE(xrGetInstanceProcAddr(InInstance, "xrDestroyTriangleMeshFB", (PFN_xrVoidFunction*)(&pfnXrDestroyTriangleMeshFB))); } } } const void* FPICOOpenXRPassthrough::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) { static FName SystemName(TEXT("OpenXR")); if (GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName)) { XRTrackingSystem = GEngine->XRSystem.Get(); } return InNext; } void FPICOOpenXRPassthrough::PostCreateSession(XrSession InSession) { Session = InSession; } void FPICOOpenXRPassthrough::UpdateDeviceLocations(XrSession InSession, XrTime DisplayTime, XrSpace TrackingSpace) { BaseSpace = TrackingSpace; TheDisplayTime = DisplayTime; if (IsInGameThread()) { ENQUEUE_RENDER_COMMAND(UpdatePassthroughLayers)( [this, PassthroughLayers = passthroughCompLayers](FRHICommandListImmediate&) { passthroughCompLayers_RenderThread.Empty(PassthroughLayers.Num()); for (auto Pair : PassthroughLayers) { passthroughCompLayers_RenderThread.Emplace(Pair.Value); } }); } } void FPICOOpenXRPassthrough::OnBeginRendering_RenderThread(XrSession InSession) { UpdateInsightPassthrough(); if (InsightInitStatus == FInsightInitStatus::Initialized) { for (const auto& layer : passthroughCompLayers_RenderThread) { if (layer.Desc.HasShape()) { const FFullScreenLayerShapePICO& FullScreenLayerProps = layer.Desc.GetShape(); UpdatePassthroughStyle_RenderThread(layer.PassthroughCompLayer.layerHandle, FullScreenLayerProps.EdgeStyleParameters); } else if (layer.Desc.HasShape()) { const FCustomShapeLayerShapePICO& CustomShapeLayerProps = layer.Desc.GetShape(); UpdatePassthroughStyle_RenderThread(layer.PassthroughCompLayer.layerHandle, CustomShapeLayerProps.EdgeStyleParameters); } if (!CustomShapeGeometryMap) { CustomShapeGeometryMap = MakeShared, ESPMode::ThreadSafe>(); } if (layer.Desc.HasShape() && BaseSpace != XR_NULL_HANDLE) { const FCustomShapeLayerShapePICO& CustomShapeLayerProps = layer.Desc.GetShape(); const TArray& UserGeometryList = CustomShapeLayerProps.UserGeometryList; TSet UsedSet; for (const FCustomShapeGeometryDescPICO& GeometryDesc : UserGeometryList) { const FString MeshName = GeometryDesc.MeshName; UsedSet.Add(MeshName); FPassthroughMesh* LayerPassthroughMesh = CustomShapeGeometryMap->Find(MeshName); if (!LayerPassthroughMesh) { FPICOPassthroughMeshRef GeomPassthroughMesh = GeometryDesc.PassthroughMesh; if (GeomPassthroughMesh) { XrTriangleMeshFB MeshHandle = 0; XrGeometryInstanceFB InstanceHandle = 0; AddPassthroughMesh_RenderThread(layer.PassthroughCompLayer.layerHandle, GeomPassthroughMesh->GetVertices(), GeomPassthroughMesh->GetTriangles(), GeometryDesc.Transform, &MeshHandle, &InstanceHandle); CustomShapeGeometryMap->Add(MeshName, FPassthroughMesh(MeshHandle, InstanceHandle)); } } else if (GeometryDesc.bUpdateTransform) { UpdatePassthroughMeshTransform_RenderThread(LayerPassthroughMesh->InstanceHandle, GeometryDesc.Transform); } } TArray ItemsToRemove; for (auto& Entry : *CustomShapeGeometryMap) { if (!UsedSet.Contains(Entry.Key)) { ItemsToRemove.Add(Entry.Key); } } for (FString Entry : ItemsToRemove) { FPassthroughMesh* PassthroughMesh = CustomShapeGeometryMap->Find(Entry); if (PassthroughMesh) { RemovePassthroughMesh_RenderThread(PassthroughMesh->MeshHandle, PassthroughMesh->InstanceHandle); } else { UE_LOG(LogTemp, Error, TEXT("PassthroughMesh: %s doesn't exist."), *Entry); return; } CustomShapeGeometryMap->Remove(Entry); } } } } } void FPICOOpenXRPassthrough::UpdatePassthroughStyle_RenderThread(XrPassthroughLayerFB LayerHandle, const FVSTEdgeStyleParametersPICO& EdgeStyleParameters) { XrPassthroughStyleFB Style = { XR_TYPE_PASSTHROUGH_STYLE_FB }; Style.edgeColor = XrColor4f{ 0 , 0 , 0 , 0 }; Style.textureOpacityFactor = EdgeStyleParameters.TextureOpacityFactor; if (EdgeStyleParameters.bEnableEdgeColor) { Style.edgeColor = ToXrColorf(EdgeStyleParameters.EdgeColor); } XrPassthroughColorMapMonoToRgbaFB MonoToRgba = { XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB }; XrPassthroughColorMapMonoToMonoFB MonoToMono = { XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB }; XrPassthroughBrightnessContrastSaturationFB BrightnessContrastSaturation = { XR_TYPE_PASSTHROUGH_BRIGHTNESS_CONTRAST_SATURATION_FB }; if (EdgeStyleParameters.bEnableColorMap) { switch (EdgeStyleParameters.ColorMapType) { case PassthroughColorMapType::NoneStyle: break; case PassthroughColorMapType::MonoToRgba: FMemory::Memcpy(MonoToRgba.textureColorMap, EdgeStyleParameters.ColorMapData.GetData(), EdgeStyleParameters.ColorMapData.Num() * sizeof(uint8)); Style.next = &MonoToRgba; break; case PassthroughColorMapType::MonoToMono: FMemory::Memcpy(MonoToMono.textureColorMap, EdgeStyleParameters.ColorMapData.GetData(), EdgeStyleParameters.ColorMapData.Num() * sizeof(uint8)); Style.next = &MonoToMono; break; case PassthroughColorMapType::HandsContrast: break; case PassthroughColorMapType::BrightnessContrastSaturation: BrightnessContrastSaturation.brightness = EdgeStyleParameters.Brightness * 100.0f; BrightnessContrastSaturation.contrast = EdgeStyleParameters.Contrast; BrightnessContrastSaturation.saturation = EdgeStyleParameters.Saturation; Style.next = &BrightnessContrastSaturation; break; default: break; } } XrResult result = pfnXrPassthroughLayerSetStyleFBX(LayerHandle, &Style); if (XR_FAILED(result)) { UE_LOG(LogPassthroughPICO, Warning, TEXT("Failed setting passthrough style")); return; } } void FPICOOpenXRPassthrough::AddPassthroughMesh_RenderThread(XrPassthroughLayerFB Layer, const TArray& Vertices, const TArray& Triangles, FTransform Transformation, XrTriangleMeshFB* OutMeshHandle, XrGeometryInstanceFB* OutInstanceHandle) { check(IsInRenderingThread()); XrTriangleMeshFB MeshHandle = 0; XrGeometryInstanceFB InstanceHandle = 0; XrTriangleMeshCreateInfoFB MeshCreateInfo = { XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB }; MeshCreateInfo.vertexCount = Vertices.Num(); float WorldToMetersScale = XRTrackingSystem->GetWorldToMetersScale(); XrVector3f* v = new XrVector3f[Vertices.Num()]; for (int i = 0; i < Vertices.Num(); i++) { v[i] = ToXrVector(Vertices[i], WorldToMetersScale); } MeshCreateInfo.vertexBuffer = v; MeshCreateInfo.triangleCount = Triangles.Num() / 3; MeshCreateInfo.indexBuffer = (uint32_t*)Triangles.GetData(); MeshCreateInfo.windingOrder = XR_WINDING_ORDER_CW_FB; if (XR_FAILED(pfnXrCreateTriangleMeshFB(Session, &MeshCreateInfo, &MeshHandle))) { UE_LOG(LogPassthroughPICO, Error, TEXT("Failed creating passthrough mesh surface.")); return; } UE_LOG(LogPassthroughPICO, Log, TEXT("Succeed creating passthrough mesh surface.")); delete []v; FTransform TransformTrackingSpace = Transformation * XRTrackingSystem->GetTrackingToWorldTransform().Inverse(); XrGeometryInstanceCreateInfoFB CreateInfo = { XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB }; CreateInfo.layer = Layer; CreateInfo.mesh = MeshHandle; CreateInfo.pose = ToXrPose(TransformTrackingSpace, WorldToMetersScale); CreateInfo.scale = ToXrVector(TransformTrackingSpace.GetScale3D()); CreateInfo.baseSpace = BaseSpace; if (XR_FAILED(pfnXrCreateGeometryInstanceF(Session, &CreateInfo, &InstanceHandle))) { UE_LOG(LogPassthroughPICO, Error, TEXT("Failed adding passthrough mesh surface to scene.")); return; } UE_LOG(LogPassthroughPICO, Log, TEXT("Succeed adding passthrough mesh surface to scene.")); *OutMeshHandle = MeshHandle; *OutInstanceHandle = InstanceHandle; } void FPICOOpenXRPassthrough::UpdatePassthroughMeshTransform_RenderThread(XrGeometryInstanceFB InstanceHandle, FTransform Transformation) { check(IsInRenderingThread()); if (InstanceHandle == XR_NULL_HANDLE || XRTrackingSystem == nullptr) { return; } float WorldToMetersScale = XRTrackingSystem->GetWorldToMetersScale(); FTransform TransformTrackingSpace = Transformation * XRTrackingSystem->GetTrackingToWorldTransform().Inverse(); XrGeometryInstanceTransformFB PoseInfo = { XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB }; PoseInfo.baseSpace = BaseSpace; PoseInfo.time = TheDisplayTime; PoseInfo.pose = ToXrPose(TransformTrackingSpace, WorldToMetersScale); PoseInfo.scale = ToXrVector(TransformTrackingSpace.GetScale3D()); if (XR_FAILED(pfnXrGeometryInstanceSetTransformFB(InstanceHandle, &PoseInfo))) { UE_LOG(LogPassthroughPICO, Error, TEXT("Failed updating passthrough mesh surface transform.")); return; } } void FPICOOpenXRPassthrough::RemovePassthroughMesh_RenderThread(XrTriangleMeshFB MeshHandle, XrGeometryInstanceFB InstanceHandle) { check(IsInRenderingThread()); if (XR_FAILED(pfnXrDestroyGeometryInstanceFB(InstanceHandle))) { UE_LOG(LogPassthroughPICO, Error, TEXT("Failed removing passthrough surface from scene.")); return; } if (XR_FAILED(pfnXrDestroyTriangleMeshFB(MeshHandle))) { UE_LOG(LogPassthroughPICO, Error, TEXT("Failed destroying passthrough surface mesh.")); return; } } void FPICOOpenXRPassthrough::OnDestroySession(XrSession InSession) { ShutdownInsightPassthrough(); } void FPICOOpenXRPassthrough::UpdateCompositionLayers(XrSession InSession, TArray& Headers) { if (bCurrentDeviceSupportedPassthrough && passthroughCompLayers_RenderThread.Num() > 0) { int InsertOrder = 0; for (int i = 0; i < passthroughCompLayers_RenderThread.Num(); i++) { bool bOverlay = false; if (passthroughCompLayers_RenderThread[i].Desc.HasShape()) { const FFullScreenLayerShapePICO& FullScreenLayerProps = passthroughCompLayers_RenderThread[i].Desc.GetShape(); bOverlay = FullScreenLayerProps.PassthroughLayerOrder == PassthroughLayerOrder_Overlay; } else if (passthroughCompLayers_RenderThread[i].Desc.HasShape()) { const FCustomShapeLayerShapePICO& CustomShapeLayerProps = passthroughCompLayers_RenderThread[i].Desc.GetShape(); bOverlay = CustomShapeLayerProps.PassthroughLayerOrder == PassthroughLayerOrder_Overlay; } if (bOverlay) { Headers.Add(reinterpret_cast(&(passthroughCompLayers_RenderThread[i].PassthroughCompLayer))); } else { Headers.Insert(reinterpret_cast(&(passthroughCompLayers_RenderThread[i].PassthroughCompLayer)), InsertOrder++); } } } } const void* FPICOOpenXRPassthrough::OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags) { if (bCurrentDeviceSupportedPassthrough) { BlendState = { XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB }; BlendState.next = const_cast(InNext); BlendState.srcFactorColor = XrBlendFactorFB::XR_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA_FB; BlendState.dstFactorColor = XrBlendFactorFB::XR_BLEND_FACTOR_SRC_ALPHA_FB; BlendState.srcFactorAlpha = XrBlendFactorFB::XR_BLEND_FACTOR_ONE_FB; BlendState.dstFactorAlpha = XrBlendFactorFB::XR_BLEND_FACTOR_ZERO_FB; InNext = &BlendState; } return InNext; } bool FPICOOpenXRPassthrough::GetSupportedPassthrough(bool& Support, bool& HasColor, bool& HasDepth) { Support = bCurrentDeviceSupportedPassthrough; HasColor = bPassthroughSupportColor; HasDepth = bPassthroughSupportDepth; return bSupportPassthroughEXT; } void FPICOOpenXRPassthrough::UpdateInsightPassthrough() { const bool bShouldEnable = (InsightInitStatus == FInsightInitStatus::NotInitialized) && (GetMutableDefault()->bEnablePassthroughEXT); if (bShouldEnable) { if (InitializeInsightPassthroughOpenXR()) { UE_LOG(LogPassthroughPICO, Log, TEXT("Passthrough Initialized")); InsightInitStatus = FInsightInitStatus::Initialized; } else { InsightInitStatus = FInsightInitStatus::Failed; UE_LOG(LogPassthroughPICO, Log, TEXT("Passthrough initialization failed")); } } else { const bool bShouldShutdown = (InsightInitStatus == FInsightInitStatus::Initialized) && (!GetMutableDefault()->bEnablePassthroughEXT); if (bShouldShutdown) { ShutdownInsightPassthrough(); } } } void FPICOOpenXRPassthrough::ShutdownInsightPassthrough() { if (InsightInitStatus == FInsightInitStatus::Initialized) { // it may already be deinitialized. if (!GetInsightPassthroughInitializedOpenXR() || ShutdownInsightPassthroughOpenXR()) { UE_LOG(LogPassthroughPICO, Log, TEXT("Passthrough shutdown")); InsightInitStatus = FInsightInitStatus::NotInitialized; } else { UE_LOG(LogPassthroughPICO, Log, TEXT("Failed to shut down passthrough. It may be still in use.")); } } } bool FPICOOpenXRPassthrough::IsInsightPassthroughSupportedOpenXR(bool* supported) { if (supported) { *supported = bCurrentDeviceSupportedPassthrough; return true; } return false; } bool FPICOOpenXRPassthrough::InitializeInsightPassthroughOpenXR() { if (Session != XR_NULL_HANDLE && bCurrentDeviceSupportedPassthrough) { XrPassthroughCreateInfoFB passthroughCreateInfo = { XR_TYPE_PASSTHROUGH_CREATE_INFO_FB }; passthroughCreateInfo.flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB; XrResult result = pfnXrCreatePassthroughFBX(Session, &passthroughCreateInfo, &passthroughFeature); if (XR_FAILED(result)) { UE_LOG(LogPassthroughPICO, Warning, TEXT("Failed to create the passthrough feature.")); return false; } return true; } return false; } bool FPICOOpenXRPassthrough::ShutdownInsightPassthroughOpenXR() { if (bCurrentDeviceSupportedPassthrough && passthroughFeature) { pfnXrDestroyPassthroughFBX(passthroughFeature); return true; } return false; } bool FPICOOpenXRPassthrough::GetInsightPassthroughInitializedOpenXR() { if (bCurrentDeviceSupportedPassthrough && passthroughFeature) { return true; } return false; } uint32 FPICOOpenXRPassthrough::CreatePassthroughLayer(const IStereoLayers::FLayerDesc& InLayerDesc) { uint32 id = 0; if (Session != XR_NULL_HANDLE && bCurrentDeviceSupportedPassthrough && passthroughFeature != XR_NULL_HANDLE) { XrPassthroughLayerFB passthroughLayer = XR_NULL_HANDLE; XrPassthroughLayerCreateInfoFB layerCreateInfo = { XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB }; layerCreateInfo.passthrough = passthroughFeature; if (InLayerDesc.HasShape()) { layerCreateInfo.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB; } else if (InLayerDesc.HasShape()) { layerCreateInfo.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB; } else { UE_LOG(LogPassthroughPICO, Warning, TEXT("No available passthrough layer shape!")); return id; } layerCreateInfo.flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB; XrResult result = pfnXrCreatePassthroughLayerFBX(Session, &layerCreateInfo, &passthroughLayer); if (XR_FAILED(result)) { UE_LOG(LogPassthroughPICO, Error, TEXT("Failed to create a passthrough layer")); return id; } UE_LOG(LogPassthroughPICO, Log, TEXT("Succeed to create a passthrough layer")); id = ++NextLayerIndex;//start from 1 FPassthroughLayer PassthroughLayer; PassthroughLayer.Id = id; PassthroughLayer.Desc = InLayerDesc; PassthroughLayer.PassthroughFeature = passthroughFeature; PassthroughLayer.PassthroughCompLayer = { XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB }; PassthroughLayer.PassthroughCompLayer.layerHandle = passthroughLayer; PassthroughLayer.PassthroughCompLayer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; PassthroughLayer.PassthroughCompLayer.space = XR_NULL_HANDLE; passthroughCompLayers.Add(id, PassthroughLayer); } return id; } bool FPICOOpenXRPassthrough::DestroyPassthroughLayer(uint32 Id) { FPassthroughLayer* LayerFound = passthroughCompLayers.Find(Id); if (LayerFound && (*LayerFound).PassthroughCompLayer.layerHandle != XR_NULL_HANDLE) { XrResult result = pfnDestroyPassthroughLayerFBX((*LayerFound).PassthroughCompLayer.layerHandle); if (XR_FAILED(result)) { UE_LOG(LogPassthroughPICO, Warning, TEXT("Failed to destroy a passthrough layer Id:%u"), Id); return false; } passthroughCompLayers.Remove(Id); UE_LOG(LogPassthroughPICO, Log, TEXT("Succeed to destroy a passthrough layer Id:%u"), Id); return true; } return false; } bool FPICOOpenXRPassthrough::PausePassthroughLayer(uint32 Id) { FPassthroughLayer* LayerFound = passthroughCompLayers.Find(Id); if (LayerFound && (*LayerFound).PassthroughCompLayer.layerHandle != XR_NULL_HANDLE) { XrResult result = pfnXrPassthroughLayerPauseFBX((*LayerFound).PassthroughCompLayer.layerHandle); if (XR_FAILED(result)) { UE_LOG(LogPassthroughPICO, Warning, TEXT("Failed to pause a passthrough layer Id:%u"), Id); return false; } return true; } return false; } bool FPICOOpenXRPassthrough::ResumePassthroughLayer(uint32 ID) { FPassthroughLayer* LayerFound = passthroughCompLayers.Find(ID); if (LayerFound && (*LayerFound).PassthroughCompLayer.layerHandle != XR_NULL_HANDLE) { XrResult result = pfnXrPassthroughLayerResumeFBX((*LayerFound).PassthroughCompLayer.layerHandle); if (XR_FAILED(result)) { UE_LOG(LogPassthroughPICO, Warning, TEXT("Failed to resume a passthrough layer Id:%u"), ID); return false; } return true; } return false; } void FPICOOpenXRPassthrough::SetLayerDesc(uint32 LayerId, const IStereoLayers::FLayerDesc& InLayerDesc) { FPassthroughLayer* LayerFound = passthroughCompLayers.Find(LayerId); if (LayerFound) { (*LayerFound).Desc = InLayerDesc; } }