using System; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; #if UNITY_EDITOR using UnityEditor; using UnityEditor.XR.OpenXR.Features; #endif namespace VIVE.OpenXR.Enterprise { #if UNITY_EDITOR [OpenXRFeature(UiName = "VIVE XR Enterprise Command", Desc = "Support Enterprise request with special command", Company = "HTC", OpenxrExtensionStrings = kOpenxrExtensionString, Version = "0.1", BuildTargetGroups = new[] { BuildTargetGroup.Android }, FeatureId = featureId, Hidden = true )] #endif public class ViveEnterpriseCommand : OpenXRFeature { #region Log const string LOG_TAG = "VIVE.OpenXR.Enterprise.Command "; private static void DEBUG(String msg) { Debug.Log(LOG_TAG + msg); } private static void ERROR(String msg) { Debug.LogError(LOG_TAG + msg); } #endregion /// /// The feature id string. This is used to give the feature a well known id for reference. /// public const string featureId = "vive.openxr.feature.enterprise.command"; /// /// The extension string. /// public const string kOpenxrExtensionString = "XR_HTC_enterprise_command"; #region OpenXR Life Cycle private static bool m_XrInstanceCreated = false; private static bool m_XrSessionCreated = false; private static XrInstance m_XrInstance = 0; private static XrSession m_XrSession = 0; private static XrSystemId m_XrSystemId = 0; /// /// Called when xrCreateInstance is done. /// /// The created instance. /// True for valid XrInstance protected override bool OnInstanceCreate(ulong xrInstance) { if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString)) { ERROR($"OnInstanceCreate() {kOpenxrExtensionString} is NOT enabled."); return false; } m_XrInstanceCreated = true; m_XrInstance = xrInstance; DEBUG($"OnInstanceCreate() {m_XrInstance}"); return GetXrFunctionDelegates(m_XrInstance); } /// /// Called when xrDestroyInstance is done. /// /// The instance to destroy. protected override void OnInstanceDestroy(ulong xrInstance) { if (m_XrInstance == xrInstance) { m_XrInstanceCreated = false; m_XrInstance = 0; } DEBUG($"OnInstanceDestroy() {xrInstance}"); } /// /// Called when the XrSystemId retrieved by xrGetSystem is changed. /// /// The system id. protected override void OnSystemChange(ulong xrSystem) { m_XrSystemId = xrSystem; DEBUG($"OnSystemChange() {m_XrSystemId}"); } /// /// Called when xrCreateSession is done. /// /// The created session ID. protected override void OnSessionCreate(ulong xrSession) { m_XrSession = xrSession; m_XrSessionCreated = true; DEBUG($"OnSessionCreate() {m_XrSession}"); } /// /// Called when xrDestroySession is done. /// /// The session ID to destroy. protected override void OnSessionDestroy(ulong xrSession) { DEBUG($"OnSessionDestroy() {xrSession}"); if (m_XrSession == xrSession) { m_XrSession = 0; m_XrSessionCreated = false; } } #endregion #region OpenXR function delegates /// xrEnterpriseCommandHTC private static ViveEnterpriseCommandHelper.xrEnterpriseCommandHTCDelegate xrEnterpriseCommandHTC; /// xrGetInstanceProcAddr private static OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr; /// /// Enterprise command request for special functionality. /// /// The request of enterprise command /// The result of enterprise command /// Return XR_SUCCESS if request successfully. False otherwise. private static XrResult EnterpriseCommandHTC(XrEnterpriseCommandBufferHTC request, ref XrEnterpriseCommandBufferHTC result) { if (!m_XrSessionCreated) { ERROR("EnterpriseCommandHTC() XR_ERROR_SESSION_LOST."); return XrResult.XR_ERROR_SESSION_LOST; } if (!m_XrInstanceCreated) { ERROR("EnterpriseCommandHTC() XR_ERROR_INSTANCE_LOST."); return XrResult.XR_ERROR_INSTANCE_LOST; } DEBUG($"EnterpriseCommandHTC() code: {request.code}, data: {CharArrayToString(request.data)}"); return xrEnterpriseCommandHTC(m_XrSession, request, ref result); } /// /// Get the OpenXR function via XrInstance. /// /// The XrInstance is provided by the Unity OpenXR Plugin. /// Return true if request successfully. False otherwise. private bool GetXrFunctionDelegates(XrInstance xrInstance) { /// xrGetInstanceProcAddr if (xrGetInstanceProcAddr != null && xrGetInstanceProcAddr != IntPtr.Zero) { DEBUG("Get function pointer of xrGetInstanceProcAddr."); XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer( xrGetInstanceProcAddr, typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate; } else { ERROR("xrGetInstanceProcAddr"); return false; } /// xrEnterpriseCommandHTC if (XrGetInstanceProcAddr(xrInstance, "xrEnterpriseCommandHTC", out IntPtr funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { DEBUG("Get function pointer of xrEnterpriseCommandHTC."); xrEnterpriseCommandHTC = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(ViveEnterpriseCommandHelper.xrEnterpriseCommandHTCDelegate)) as ViveEnterpriseCommandHelper.xrEnterpriseCommandHTCDelegate; } } else { ERROR("xrEnterpriseCommandHTC"); return false; } return true; } #endregion #region Public API private const int kCharLength = 256; private const char kEndChar = '\0'; private static char[] charArray = new char[kCharLength]; /// /// Request special feature with command, it should take code and command string. /// /// The type of request code is integer. /// The maximum length of request command is 256. /// The output of result code. /// The output of result command. /// Return true if request successfully. False otherwise. public static bool CommandRequest(int requestCode, string requestCommand, out int resultCode, out string resultCommand) { resultCode = 0; resultCommand = string.Empty; XrEnterpriseCommandBufferHTC request = new XrEnterpriseCommandBufferHTC(requestCode, StringToCharArray(requestCommand)); XrEnterpriseCommandBufferHTC result = new XrEnterpriseCommandBufferHTC(resultCode, StringToCharArray(resultCommand)); if (EnterpriseCommandHTC(request, ref result) == XrResult.XR_SUCCESS) { resultCode = result.code; resultCommand = CharArrayToString(result.data); DEBUG($"CommandRequest Result code: {resultCode}, data: {resultCommand}"); return true; } return false; } #endregion private static char[] StringToCharArray(string str) { Array.Clear(charArray, 0, kCharLength); if (!string.IsNullOrEmpty(str)) { int arrayLength = Math.Min(str.Length, kCharLength); for (int i = 0; i < arrayLength; i++) { charArray[i] = str[i]; } charArray[kCharLength - 1] = kEndChar; } return charArray; } private static string CharArrayToString(char[] charArray) { int actualLength = Array.FindIndex(charArray, c => c == kEndChar); if (actualLength == -1) { actualLength = charArray.Length; } return new string(charArray, 0, actualLength); } } #region Helper public struct XrEnterpriseCommandBufferHTC { public Int32 code; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] public char[] data; public XrEnterpriseCommandBufferHTC(int in_code, char[] in_data) { code = (Int32)in_code; data = new char[in_data.Length]; Array.Copy(in_data, data, in_data.Length); } } public class ViveEnterpriseCommandHelper { /// /// The function delegate of xrEnterpriseCommandHTC. /// /// An XrSession in which the enterprise command will be active. /// The request of enterprise command /// The result of enterprise command /// Return XR_SUCCESS if request successfully. False otherwise. public delegate XrResult xrEnterpriseCommandHTCDelegate( XrSession session, XrEnterpriseCommandBufferHTC request, ref XrEnterpriseCommandBufferHTC result); } #endregion }