// %BANNER_BEGIN% // --------------------------------------------------------------------- // %COPYRIGHT_BEGIN% // Copyright (c) (2024) Magic Leap, Inc. All Rights Reserved. // Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2 // Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein. // %COPYRIGHT_END% // --------------------------------------------------------------------- // %BANNER_END% using System; using System.Collections.Generic; using MagicLeap.OpenXR.NativeDelegates; using MagicLeap.OpenXR.SystemInfo; using MagicLeap.OpenXR.ViewConfiguration; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using UnityEngine.XR.MagicLeap.Unsafe; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.NativeTypes; #if UNITY_EDITOR using UnityEditor; using UnityEditor.XR.OpenXR.Features; #endif namespace MagicLeap.OpenXR.Features.PhysicalOcclusion { /// /// Enables the Magic Leap OpenXR Loader for Android, and modifies the AndroidManifest to be compatible with ML2. /// #if UNITY_EDITOR [OpenXRFeature(UiName = "Magic Leap 2 Physical Occlusion", Desc = "Necessary to deploy a Magic Leap 2 compatible application with physical world occlusion", Company = "Magic Leap", Version = "1.0.0", FeatureId = FeatureId, BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone }, OpenxrExtensionStrings = OcclusionExtensionName )] #endif public unsafe class MagicLeapPhysicalOcclusionFeature : MagicLeapOpenXRFeatureWithInterception { public const string FeatureId = "com.magicleap.openxr.feature.ml2_physical_occlusion"; private const string OcclusionExtensionName = "XR_ML_physical_world_occlusion"; protected override bool UsesExperimentalExtensions => true; /// /// The Occlusion sources to use for physical occlusion /// [Flags] public enum OcclusionSource { Environment = 1 << 1, DepthSensor = 1 << 2, Hands = 1 << 3, Controller = 1 << 4, } private OcclusionSource enabledOcclusionSources; /// /// Occlusion Sources to enable /// public OcclusionSource EnabledOcclusionSource { get => enabledOcclusionSources; set { enabledOcclusionSources = value; UpdateOcclusionSources(); } } /// /// Whether to enable or disable occlusion /// public bool EnableOcclusion { get; set; } /// /// The near range of the depth sensor, if the depth sensor is used as an occlusion source /// public float DepthSensorNearRange { get => sourcesHolder->DepthSensor.NearRange; set { var clampedValue = Mathf.Clamp(value, occlusionPropertiesHolder.DepthSensorProperties.MinNearRange, occlusionPropertiesHolder.DepthSensorProperties.MaxNearRange); sourcesHolder->DepthSensor.NearRange = clampedValue; } } /// /// The far range of the depth sensor, if the depth sensor is used as an occlusion source /// public float DepthSensorFarRange { get => sourcesHolder->DepthSensor.FarRange; set { var clampedValue = Mathf.Clamp(value, occlusionPropertiesHolder.DepthSensorProperties.MinFarRange, occlusionPropertiesHolder.DepthSensorProperties.MaxFarRange); sourcesHolder->DepthSensor.FarRange = clampedValue; } } private XrPhysicalOcclusionPropertiesHolder occlusionPropertiesHolder; private ViewConfigNativeFunctions viewConfigNativeFunctions; private SystemInfoNativeFunctions systemInfoNativeFunctions; private XrPhysicalOcclusionSourcesHolder* sourcesHolder; private NativeList occlusionSources; private XrCompositionLayerPhysicalWorldOcclusion* physicalWorldOcclusionCompositionLayer; protected override bool OnInstanceCreate(ulong xrInstance) { var creationResult = base.OnInstanceCreate(xrInstance); if (!OpenXRRuntime.IsExtensionEnabled(OcclusionExtensionName)) { Debug.LogError($"{OcclusionExtensionName} extension was not enabled"); return false; } if (!creationResult) { return false; } viewConfigNativeFunctions = CreateNativeFunctions(); systemInfoNativeFunctions = CreateNativeFunctions(); var xrResult = systemInfoNativeFunctions.GetSystemId(out var systemId); if (!Utils.DidXrCallSucceed(xrResult, nameof(systemInfoNativeFunctions.GetSystemId))) { return false; } Utils.OpenXRStructHelpers.Create(StructType.ViewConfigurationProperties, out var viewConfigurationProperties); var propertiesHolder = XrPhysicalOcclusionPropertiesHolder.Create(); viewConfigurationProperties.Next = new IntPtr(&propertiesHolder.ControllerProperties); propertiesHolder.ControllerProperties.Next = new IntPtr(&propertiesHolder.EnvironmentProperties); propertiesHolder.EnvironmentProperties.Next = new IntPtr(&propertiesHolder.HandsProperties); propertiesHolder.HandsProperties.Next = new IntPtr(&propertiesHolder.DepthSensorProperties); xrResult = viewConfigNativeFunctions.GetViewConfigurationProperties(AppInstance, systemId, ref viewConfigurationProperties); if (!Utils.DidXrCallSucceed(xrResult, nameof(viewConfigNativeFunctions.GetViewConfigurationProperties))) { return false; } occlusionPropertiesHolder = propertiesHolder; AllocateOrFreeResources(true); return true; } private void UpdateOcclusionSources() { occlusionSources.Clear(); foreach (var occlusionSource in (OcclusionSource[])Enum.GetValues(typeof(OcclusionSource))) { if (enabledOcclusionSources.HasFlag(occlusionSource)) { switch (occlusionSource) { case OcclusionSource.Environment: occlusionSources.Add(new IntPtr(&sourcesHolder->Environment)); break; case OcclusionSource.DepthSensor: occlusionSources.Add(new IntPtr(&sourcesHolder->DepthSensor)); break; case OcclusionSource.Hands: occlusionSources.Add(new IntPtr(&sourcesHolder->Hands)); break; case OcclusionSource.Controller: occlusionSources.Add(new IntPtr(&sourcesHolder->Controller)); break; default: throw new ArgumentOutOfRangeException(); } } } physicalWorldOcclusionCompositionLayer->OcclusionSourceCount = (uint)occlusionSources.Length; physicalWorldOcclusionCompositionLayer->OcclusionSources = occlusionSources.GetUnsafePtr(); } protected override void OnInstanceDestroy(ulong xrInstance) { base.OnInstanceDestroy(xrInstance); AllocateOrFreeResources(false); } protected override void MarkFunctionsToIntercept() { InterceptEndFrame = true; } private void AllocateOrFreeResources(bool shouldAllocate) { if (shouldAllocate) { occlusionSources = new NativeList(4, Allocator.Persistent); sourcesHolder = UnsafeUtilityEx.CallocTracked(Allocator.Persistent); sourcesHolder->Initialize(in occlusionPropertiesHolder); physicalWorldOcclusionCompositionLayer = UnsafeUtilityEx.CallocTracked(Allocator.Persistent); physicalWorldOcclusionCompositionLayer->Type = XrPhysicalOcclusionStructTypes.XRTypeCompositionLayerPhysicalWorldOcclusion; } else { occlusionSources.Dispose(); UnsafeUtility.FreeTracked(sourcesHolder, Allocator.Persistent); UnsafeUtility.FreeTracked(physicalWorldOcclusionCompositionLayer, Allocator.Persistent); } } /// /// Checks if an occlusion source is supported /// /// The occlusion source to check /// True if the occlusion source is supported /// Throws exception if an invalid occlusion source is passed public bool IsOcclusionSourceSupported(OcclusionSource source) { return source switch { OcclusionSource.Environment => occlusionPropertiesHolder.EnvironmentProperties.SupportsOcclusion, OcclusionSource.DepthSensor => occlusionPropertiesHolder.DepthSensorProperties.SupportsOcclusion, OcclusionSource.Hands => occlusionPropertiesHolder.HandsProperties.SupportsOcclusion, OcclusionSource.Controller => occlusionPropertiesHolder.ControllerProperties.SupportsOcclusion, _ => throw new ArgumentOutOfRangeException(nameof(source), source, "Invalid Occlusion Source") }; } /// /// Get the near and far range ranges for the depth sensor occlusion /// /// A tuple of named-tuple pairs for the near-range and the far-range values public ((float min, float max) nearRange, (float min, float max) farRange) GetDepthSensorProperties() { ref var depthSensorProperties = ref occlusionPropertiesHolder.DepthSensorProperties; return ((depthSensorProperties.MinNearRange, depthSensorProperties.MaxNearRange), (depthSensorProperties.MinFarRange, depthSensorProperties.MaxFarRange)); } internal override XrResult OnEndFrame(ulong session, XrFrameEndInfo* frameEndInfo, XrEndFrame origEndFrame) { if (EnableOcclusion && occlusionSources.Length > 0) { frameEndInfo->AppendToProjectionLayer(new IntPtr(physicalWorldOcclusionCompositionLayer), includeSecondaryViews: true); } return base.OnEndFrame(session, frameEndInfo, origEndFrame); } #if UNITY_EDITOR protected override void GetValidationChecks(List rules, BuildTargetGroup targetGroup) { rules.Add(Utils.GetDepthSubmissionValidationRule(this, targetGroup)); base.GetValidationChecks(rules, targetGroup); } #endif } }