using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using UnityEngine.Serialization;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.XR.OpenXR.Input;
using UnityEngine.XR.OpenXR.NativeTypes;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR;
using UnityEditor.XR.OpenXR.Features;
using System.Linq;
#endif
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Editor")]
[assembly: InternalsVisibleTo("UnityEditor.XR.OpenXR.Tests")]
namespace UnityEngine.XR.OpenXR.Features
{
///
/// A Unity OpenXR Feature.
/// This class can be inherited from to add feature specific data and logic.
/// Feature-specific settings are serialized for access at runtime.
///
[Serializable]
public abstract partial class OpenXRFeature : ScriptableObject
{
#if UNITY_EDITOR
internal static Func canSetFeatureDisabled;
#endif
///
/// Feature will be enabled when OpenXR is initialized.
///
[FormerlySerializedAs("enabled")] [HideInInspector] [SerializeField] private bool m_enabled = false;
internal bool failedInitialization { get; private set; } = false;
///
/// True if a required feature failed initialization, false if all features initialized successfully.
///
internal static bool requiredFeatureFailed { get; private set; }
///
/// Feature is enabled and will be started when the OpenXR loader is initialized.
///
/// Note that the enabled state of a feature cannot be modified once OpenXR is initialized and
/// can be used at runtime to determine if a feature successfully initialized.
///
public bool enabled
{
get => m_enabled && (OpenXRLoaderBase.Instance == null || !failedInitialization);
set
{
if (enabled == value)
return;
#if UNITY_EDITOR
if (canSetFeatureDisabled != null && !value && !canSetFeatureDisabled.Invoke(featureIdInternal))
return;
#endif //UNITY_EDITOR
if (OpenXRLoaderBase.Instance != null)
{
Debug.LogError("OpenXRFeature.enabled cannot be changed while OpenXR is running");
return;
}
m_enabled = value;
OnEnabledChange();
}
}
///
/// Automatically filled out by the build process from OpenXRFeatureAttribute.
/// Name of the feature.
///
[HideInInspector] [SerializeField] internal string nameUi = null;
///
/// Automatically filled out by the build process from OpenXRFeatureAttribute.
/// Version of the feature.
///
[HideInInspector] [SerializeField] internal string version = null;
///
/// Feature id.
///
[HideInInspector] [SerializeField] internal string featureIdInternal = null;
///
/// Automatically filled out by the build process from OpenXRFeatureAttribute.
/// OpenXR runtime extension strings that need to be enabled to use this extension.
/// May contain multiple extensions separated by spaces.
///
[HideInInspector] [SerializeField] internal string openxrExtensionStrings = null;
///
/// Automatically filled out by the build process from OpenXRFeatureAttribute.
/// Company name of the author of the feature.
///
[HideInInspector] [SerializeField] internal string company = null;
///
/// Automatically filled out by the build process from OpenXRFeatureAttribute.
/// Priority of the feature.
///
[HideInInspector] [SerializeField] internal int priority = 0;
///
/// Automatically filled out by the build process from OpenXRFeatureAttribute.
/// True if the feature is required, false otherwise.
///
[HideInInspector] [SerializeField] internal bool required = false;
///
/// Set to true if the internal fields have been updated in the current domain
///
[NonSerialized]
internal bool internalFieldsUpdated = false;
///
/// Accessor for xrGetInstanceProcAddr function pointer.
///
protected static IntPtr xrGetInstanceProcAddr => Internal_GetProcAddressPtr(false);
///
/// Called to hook xrGetInstanceProcAddr.
/// Returning a different function pointer allows intercepting any OpenXR method.
///
/// xrGetInstanceProcAddr native function pointer
/// Function pointer that Unity will use to look up OpenXR native functions.
protected internal virtual IntPtr HookGetInstanceProcAddr(IntPtr func) => func;
///
/// Called after the OpenXR Loader is initialized and has created its subsystems.
///
protected internal virtual void OnSubsystemCreate() { }
///
/// Called after the OpenXR loader has started its subsystems.
///
protected internal virtual void OnSubsystemStart() { }
///
/// Called before the OpenXR loader stops its subsystems.
///
protected internal virtual void OnSubsystemStop() { }
///
/// Called before the OpenXR loader destroys its subsystems.
///
protected internal virtual void OnSubsystemDestroy() { }
///
/// Called after `xrCreateInstance`. Override this method to validate that any necessary OpenXR extensions were
/// successfully enabled (OpenXRRuntime.IsExtensionEnabled)
/// and that any required system properties are supported. If this method returns ,
/// the feature's property is set to .
///
/// Handle of the native `xrInstance`.
/// if this feature successfully initialized. Otherwise, .
///
/// If this feature is a required feature of an enabled feature set, returning here
/// causes the `OpenXRLoader` to fail, and XR Plug-in Management will fall back to another loader if enabled.
///
/// Enabling OpenXR spec extension strings
protected internal virtual bool OnInstanceCreate(ulong xrInstance) => true;
///
/// Called after xrGetSystem.
///
/// Handle of the xrSystemId
protected internal virtual void OnSystemChange(ulong xrSystem) { }
///
/// Called after xrCreateSession.
///
/// Handle of the xrSession
protected internal virtual void OnSessionCreate(ulong xrSession) { }
///
/// Called when the reference xrSpace for the app changes.
///
/// Handle of the xrSpace
protected internal virtual void OnAppSpaceChange(ulong xrSpace) { }
///
/// Called when the OpenXR loader receives the XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED event
/// from the runtime signaling that the XrSessionState has changed.
///
/// Previous state
/// New state
protected internal virtual void OnSessionStateChange(int oldState, int newState) { }
///
/// Called after xrSessionBegin.
///
/// Handle of the xrSession
protected internal virtual void OnSessionBegin(ulong xrSession) { }
///
/// Called before xrEndSession.
///
/// Handle of the xrSession
protected internal virtual void OnSessionEnd(ulong xrSession) { }
///
/// Called when the runtime transitions to the XR_SESSION_STATE_EXITING state.
///
/// Handle of the xrSession
protected internal virtual void OnSessionExiting(ulong xrSession) { }
///
/// Called before xrDestroySession.
///
/// Handle of the xrSession
protected internal virtual void OnSessionDestroy(ulong xrSession) { }
///
/// Called before xrDestroyInstance
///
/// Handle of the xrInstance
protected internal virtual void OnInstanceDestroy(ulong xrInstance) { }
///
/// Called when the runtime transitions to the XR_SESSION_STATE_LOSS_PENDING
/// state. This is a notification to the feature implementer that the session is
/// about to be lost. This feature should do what it needs to do to
/// prepare for potential session recreation.
///
/// The session that is going to be lost
protected internal virtual void OnSessionLossPending(ulong xrSession) { }
///
/// Called when the OpenXR loader receives the XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING event
/// from the runtime. This is a notification to the feature implementer that the instance is
/// about to be lost. This feature should do what it needs to do to
/// clean up in preparation for termination.
///
/// The instance that is going to be lost
protected internal virtual void OnInstanceLossPending(ulong xrInstance) { }
///
/// Notification to the feature implementer that the form factor has changed.
///
/// New form factor value
protected internal virtual void OnFormFactorChange(int xrFormFactor) { }
///
/// Notification to the feature implementer that the view configuration type has changed.
///
/// New view configuration type
protected internal virtual void OnViewConfigurationTypeChange(int xrViewConfigurationType) { }
///
/// Notification to the feature implementer that the environment blend mode has changed.
///
/// New environment blend mode value
protected internal virtual void OnEnvironmentBlendModeChange(XrEnvironmentBlendMode xrEnvironmentBlendMode) { }
///
/// Called when the enabled state of a feature changes
///
protected internal virtual void OnEnabledChange()
{
}
///
/// Converts an XrPath to a string.
///
/// Path to convert
/// String that represents the path, or null if the path is invalid.
protected static string PathToString(ulong path) =>
Internal_PathToStringPtr(path, out var stringPtr) ? Marshal.PtrToStringAnsi(stringPtr) : null;
///
/// Converts a string to an XrPath.
///
/// String to convert
/// Path of converted string, or XrPath.none if string could not be converted.
protected static ulong StringToPath(string str) =>
Internal_StringToPath(str, out var id) ? id : 0ul;
///
/// Returns the path of the current interaction profile for the given user path.
///
/// OpenXR User Path (eg: /user/hand/left)
/// A path to the interaction profile, or XrPath.none if the path could not be retrieved.
protected static ulong GetCurrentInteractionProfile(ulong userPath) =>
Internal_GetCurrentInteractionProfile(userPath, out ulong profileId) ? profileId : 0ul;
///
/// Returns the path of the current interaction profile for the given user path.
///
/// User path
/// A path to the interaction profile, or XrPath.none if the path could not be retrieved.
protected static ulong GetCurrentInteractionProfile(string userPath) =>
GetCurrentInteractionProfile(StringToPath(userPath));
///
/// Returns the current app space.
///
/// Current app space
protected static ulong GetCurrentAppSpace() =>
Internal_GetAppSpace(out ulong appSpaceId) ? appSpaceId : 0ul;
///
/// Returns viewConfigurationType for the given renderPass index.
///
/// RenderPass index
/// viewConfigurationType for certain renderPass. Return 0 if invalid renderPass.
protected static int GetViewConfigurationTypeForRenderPass(int renderPassIndex) =>
Internal_GetViewTypeFromRenderIndex(renderPassIndex);
///
/// Set the current XR Environment Blend Mode if it is supported by the active runtime. If not supported, fall back to the runtime preference.
///
/// Environment Blend Mode (e.g.: Opaque = 1, Additive = 2, AlphaBlend = 3)
protected static void SetEnvironmentBlendMode(XrEnvironmentBlendMode xrEnvironmentBlendMode) =>
Internal_SetEnvironmentBlendMode(xrEnvironmentBlendMode);
///
/// Returns the current XR Environment Blend Mode.
///
/// Current XR Environment Blend Mode
protected static XrEnvironmentBlendMode GetEnvironmentBlendMode() =>
Internal_GetEnvironmentBlendMode();
#if UNITY_EDITOR
///
/// A Build-time validation rule.
///
public class ValidationRule
{
///
/// Creates a validation rule for an OpenXRFeature.
///
/// Feature to create validation rule for
public ValidationRule(OpenXRFeature feature)
{
if (feature == null)
throw new Exception("Invalid feature");
this.feature = feature;
}
internal ValidationRule()
{}
///
/// Message describing the rule that will be showed to the developer if it fails.
///
public string message;
///
/// Lambda function that returns true if validation passes, false if validation fails.
///
public Func checkPredicate;
///
/// Lambda function that fixes the issue, if possible.
///
public Action fixIt;
///
/// Text describing how the issue is fixed, shown in a tooltip.
///
public string fixItMessage;
///
/// True if the fixIt Lambda function performs a function that is automatic and does not require user input. If your fixIt
/// function requires user input, set fixitAutomatic to false to prevent the fixIt method from being executed during fixAll
///
public bool fixItAutomatic = true;
///
/// If true, failing the rule is treated as an error and stops the build.
/// If false, failing the rule is treated as a warning and it doesn't stop the build. The developer has the option to correct the problem, but is not required to.
///
public bool error;
///
/// If true, will deny the project from entering playmode in editor.
/// If false, can still enter playmode in editor if this issue isn't fixed.
///
public bool errorEnteringPlaymode;
///
/// Optional text to display in a help icon with the issue in the validator.
///
public string helpText;
///
/// Optional link that will be opened if the help icon is clicked.
///
public string helpLink;
///
/// Optional struct HighlighterFocusData used to create HighlighterFocus functionality.
/// WindowTitle contains the name of the window tab to highlight in.
/// SearchPhrase contains the text to be searched and highlighted.
///
public HighlighterFocusData highlighterFocus { get; set; }
public struct HighlighterFocusData
{
public string windowTitle { get; set; }
public string searchText { get; set; }
}
internal OpenXRFeature feature;
internal BuildTargetGroup buildTargetGroup = BuildTargetGroup.Unknown;
}
///
/// Allows a feature to add to a list of validation rules which your feature will evaluate at build time.
/// Details of the validation results can be found in OpenXRProjectValidation.
///
/// Your feature will check the rules in this list at build time. Add rules that you want your feature to check, and remove rules that you want your feature to ignore.
/// Build target group these validation rules will be evaluated for.
protected internal virtual void GetValidationChecks(List rules, BuildTargetGroup targetGroup)
{
}
internal static void GetFullValidationList(List rules, BuildTargetGroup targetGroup)
{
var openXrSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(targetGroup);
if (openXrSettings == null)
{
return;
}
var tempList = new List();
foreach (var feature in openXrSettings.features)
{
if (feature != null)
{
feature.GetValidationChecks(tempList, targetGroup);
rules.AddRange(tempList);
tempList.Clear();
}
}
}
internal static void GetValidationList(List rules, BuildTargetGroup targetGroup)
{
var openXrSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(targetGroup);
if (openXrSettings == null)
{
return;
}
var features = openXrSettings.features.Where(f => f != null)
.OrderByDescending(f => f.priority)
.ThenBy(f => f.nameUi);
foreach (var feature in features)
{
if (feature != null && feature.enabled)
feature.GetValidationChecks(rules, targetGroup);
}
}
#endif
///
/// Creates a subsystem based on a given a list of descriptors and a specific subsystem id.
/// Promoted to public for extensions.
///
///
/// The descriptor type being passed in
/// The subsystem type being requested
/// List of TDescriptor instances to use for subsystem matching
/// The identifier key of the particular subsystem implementation being requested
protected void CreateSubsystem(List descriptors, string id)
where TDescriptor : ISubsystemDescriptor
where TSubsystem : ISubsystem
{
if (OpenXRLoaderBase.Instance == null)
{
Debug.LogError("CreateSubsystem called before loader was initialized");
return;
}
OpenXRLoaderBase.Instance.CreateSubsystem(descriptors, id);
}
///
/// Start a subsystem instance of a given type. Subsystem is assumed to already be loaded from
/// a previous call to CreateSubsystem.
/// Promoted to public for extensions.
///
///
/// A subclass of
protected void StartSubsystem() where T : class, ISubsystem
{
if (OpenXRLoaderBase.Instance == null)
{
Debug.LogError("StartSubsystem called before loader was initialized");
return;
}
OpenXRLoaderBase.Instance.StartSubsystem();
}
///
/// Stops a subsystem instance of a given type. Subsystem is assumed to already be loaded from
/// a previous call to CreateSubsystem.
/// Promoted to public for extensions.
///
///
/// A subclass of
protected void StopSubsystem() where T : class, ISubsystem
{
if (OpenXRLoaderBase.Instance == null)
{
Debug.LogError("StopSubsystem called before loader was initialized");
return;
}
OpenXRLoaderBase.Instance.StopSubsystem();
}
///
/// Destroys a subsystem instance of a given type. Subsystem is assumed to already be loaded from
/// a previous call to CreateSubsystem.
/// Promoted to public for extensions.
///
///
/// A subclass of
protected void DestroySubsystem() where T : class, ISubsystem
{
if (OpenXRLoaderBase.Instance == null)
{
Debug.LogError("DestroySubsystem called before loader was initialized");
return;
}
OpenXRLoaderBase.Instance.DestroySubsystem();
}
/// Called when the object is loaded.
///
/// Additional information:
/// ScriptableObject.OnEnable
///
protected virtual void OnEnable()
{
}
/// Called when the object is loaded.
///
/// Additional information:
/// ScriptableObject.OnDisable
///
protected virtual void OnDisable()
{
// Virtual for future expansion and to match OnEnable
}
/// Called when the object is loaded.
///
/// Additional information:
/// ScriptableObject.Awake
///
protected virtual void Awake()
{
}
internal enum LoaderEvent
{
SubsystemCreate,
SubsystemDestroy,
SubsystemStart,
SubsystemStop,
}
internal static bool ReceiveLoaderEvent(OpenXRLoaderBase loader, LoaderEvent e)
{
var instance = OpenXRSettings.Instance;
if (instance == null)
return true;
foreach (var feature in instance.features)
{
if (feature == null || !feature.enabled)
continue;
switch (e)
{
case LoaderEvent.SubsystemCreate:
feature.OnSubsystemCreate();
break;
case LoaderEvent.SubsystemDestroy:
feature.OnSubsystemDestroy();
break;
case LoaderEvent.SubsystemStart:
feature.OnSubsystemStart();
break;
case LoaderEvent.SubsystemStop:
feature.OnSubsystemStop();
break;
default:
throw new ArgumentOutOfRangeException(nameof(e), e, null);
}
}
return true;
}
// Must be kept in sync with unity_type.h ScriptEvents
internal enum NativeEvent
{
// Setup
XrSetupConfigValues,
XrSystemIdChanged,
XrInstanceChanged,
XrSessionChanged,
XrBeginSession,
// Runtime
XrSessionStateChanged,
XrChangedSpaceApp,
// Shutdown
XrEndSession,
XrDestroySession,
XrDestroyInstance,
// General Session Events
XrIdle,
XrReady,
XrSynchronized,
XrVisible,
XrFocused,
XrStopping,
XrExiting,
XrLossPending,
XrInstanceLossPending,
XrRestartRequested,
XrRequestRestartLoop,
XrRequestGetSystemLoop,
};
internal static void ReceiveNativeEvent(NativeEvent e, ulong payload)
{
if (null == OpenXRSettings.Instance)
return;
foreach (var feature in OpenXRSettings.Instance.features)
{
if (feature == null || !feature.enabled)
continue;
switch (e)
{
case NativeEvent.XrSetupConfigValues:
feature.OnFormFactorChange(Internal_GetFormFactor());
feature.OnEnvironmentBlendModeChange(Internal_GetEnvironmentBlendMode());
feature.OnViewConfigurationTypeChange(Internal_GetViewConfigurationType());
break;
case NativeEvent.XrSystemIdChanged:
feature.OnSystemChange(payload);
break;
case NativeEvent.XrInstanceChanged:
feature.failedInitialization = !feature.OnInstanceCreate(payload);
requiredFeatureFailed |= (feature.required && feature.failedInitialization);
break;
case NativeEvent.XrSessionChanged:
feature.OnSessionCreate(payload);
break;
case NativeEvent.XrBeginSession:
feature.OnSessionBegin(payload);
break;
case NativeEvent.XrChangedSpaceApp:
feature.OnAppSpaceChange(payload);
break;
case NativeEvent.XrSessionStateChanged:
Internal_GetSessionState(out var oldState, out var newState);
feature.OnSessionStateChange(oldState, newState);
break;
case NativeEvent.XrEndSession:
feature.OnSessionEnd(payload);
break;
case NativeEvent.XrExiting:
feature.OnSessionExiting(payload);
break;
case NativeEvent.XrDestroySession:
feature.OnSessionDestroy(payload);
break;
case NativeEvent.XrDestroyInstance:
feature.OnInstanceDestroy(payload);
break;
case NativeEvent.XrLossPending:
feature.OnSessionLossPending(payload);
break;
case NativeEvent.XrInstanceLossPending:
feature.OnInstanceLossPending(payload);
break;
}
}
}
internal static void Initialize()
{
requiredFeatureFailed = false;
var instance = OpenXRSettings.Instance;
if (instance == null || instance.features == null)
return;
foreach (var feature in instance.features)
if (feature != null)
feature.failedInitialization = false;
}
internal static void HookGetInstanceProcAddr()
{
var procAddr = Internal_GetProcAddressPtr(true);
var instance = OpenXRSettings.Instance;
if (instance != null && instance.features != null)
{
// Hook the features in reverse priority order to ensure the highest priority feature is
// hooked last. This will ensure the highest priority feature is called first in the chain.
for (var featureIndex = instance.features.Length - 1; featureIndex >= 0; featureIndex--)
{
var feature = instance.features[featureIndex];
if (feature == null || !feature.enabled)
continue;
procAddr = feature.HookGetInstanceProcAddr(procAddr);
}
}
Internal_SetProcAddressPtrAndLoadStage1(procAddr);
}
///
/// Returns XrAction handle bound to the given .
///
/// Action to retrieve XrAction handles for
/// XrAction handle bound to the given or 0 if there is no bound XrAction
protected ulong GetAction(InputAction inputAction) => OpenXRInput.GetActionHandle(inputAction);
///
/// Returns XrAction handle bound to the given device and usage.
///
/// Device to retrieve XrAction handles for
/// Usage to retrieve XrAction handles for
/// XrAction handle bound to the given device and usage, or 0 if there is no bound XrAction
protected ulong GetAction(InputDevice device, InputFeatureUsage usage) => OpenXRInput.GetActionHandle(device, usage);
///
/// Returns XrAction handle bound to the given device and usage.
///
/// Device to retrieve XrAction handles for
/// Usage name to retrieve XrAction handles for
/// XrAction handle bound to the given device and usage, or 0 if there is no bound XrAction
protected ulong GetAction(InputDevice device, string usageName) => OpenXRInput.GetActionHandle(device, usageName);
///
/// Flags that control various options and behaviors on registered stats.
///
[System.Flags]
protected internal enum StatFlags
{
///
/// Stat will have no special options or behaviors
///
StatOptionNone = 0,
///
/// Stat will clear to 0.0f at the beginning of every frame
///
ClearOnUpdate = 1 << 0,
///
/// Stat will have all special options and behaviors
///
All = (1 << 1) - 1
}
///
/// Registers an OpenXR statistic with the given name and flags.
/// This method is not thread safe, so it should only be called at OnInstanceCreate.
///
/// String identifier for the statistic.
/// Properties to be applied to the statistic.
/// Stat Id
protected internal static ulong RegisterStatsDescriptor(string statName, StatFlags statFlags)
{
return runtime_RegisterStatsDescriptor(statName, statFlags);
}
///
/// Assigns a float value to a registered statistic. Its thread safe.
///
/// Identifier of the previously registered statistic.
/// Float value to be assigned to the stat.
protected internal static void SetStatAsFloat(ulong statId, float value)
{
runtime_SetStatAsFloat(statId, value);
}
///
/// Assigns an unsigned integer value to a registered statistic. It is thread safe.
///
///
/// IMPORTANT: Due to limitations in native code, values over 16777216 (1<<24) might not be reflected accurately.
///
/// Identifier of the previously registered statistic.
/// Unsigned integer value to be assigned to the stat.
protected internal static void SetStatAsUInt(ulong statId, uint value)
{
runtime_SetStatAsUInt(statId, value);
}
}
}