using System; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; using UnityEngine.Android; using UnityEngine.Rendering; using UnityEngine.XR.Management; using UnityEngine.XR; #if UNITY_INPUT_SYSTEM && ENABLE_VR using UnityEngine.InputSystem; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.XR; using Unity.XR.Oculus.Input; #endif #if UNITY_EDITOR using System.IO; using System.Linq; using UnityEditor; #endif namespace Unity.XR.Oculus { #if UNITY_INPUT_SYSTEM && ENABLE_VR #if UNITY_EDITOR [InitializeOnLoad] #endif static class InputLayoutLoader { static InputLayoutLoader() { RegisterInputLayouts(); } public static void RegisterInputLayouts() { InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(XRUtilities.InterfaceMatchAnyVersion) .WithProduct("^(Oculus Rift)|^(Oculus Quest)|^(Oculus Go)")); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(XRUtilities.InterfaceMatchAnyVersion) .WithProduct(@"(^(Oculus Touch Controller))|(^(Oculus Quest Controller))")); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(XRUtilities.InterfaceMatchAnyVersion) .WithProduct(@"Oculus Remote")); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(XRUtilities.InterfaceMatchAnyVersion) .WithProduct(@"((Tracking Reference)|(^(Oculus Rift [a-zA-Z0-9]* \(Camera)))")); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(XRUtilities.InterfaceMatchAnyVersion) .WithProduct("^(Oculus Tracked Remote)")); InputSystem.RegisterLayout(); InputSystem.RegisterLayout(); } } #endif public class OculusLoader : XRLoaderHelper #if UNITY_EDITOR , IXRLoaderPreInit #endif { private enum DeviceSupportedResult { Supported, // we should attempt to initialize and run in VR NotSupported, // we shouldn't attempt to initialize on this device, and should run in non-VR ExitApplication // we're built for VR, but are on a non-Oculus Android device and should exit the application }; static DeviceSupportedResult IsDeviceSupported() { #if !ENABLE_VR return DeviceSupportedResult.NotSupported; #elif UNITY_EDITOR_WIN return DeviceSupportedResult.Supported; #elif (UNITY_STANDALONE_WIN && !UNITY_EDITOR) return DeviceSupportedResult.Supported; #elif (UNITY_ANDROID && !UNITY_EDITOR) try { if (NativeMethods.GetIsSupportedDevice()) return DeviceSupportedResult.Supported; else return DeviceSupportedResult.ExitApplication; } catch(DllNotFoundException) { // return NotSupported since we've been built with Oculus XR Plugin disabled return DeviceSupportedResult.NotSupported; } #else return DeviceSupportedResult.NotSupported; #endif } #if ENABLE_VR private static List s_DisplaySubsystemDescriptors = new List(); private static List s_InputSubsystemDescriptors = new List(); public XRDisplaySubsystem displaySubsystem { get { return GetLoadedSubsystem(); } } public XRInputSubsystem inputSubsystem { get { return GetLoadedSubsystem(); } } #endif public override bool Initialize() { if (!CheckUnityVersionCompatibility()) { return false; } if (IsDeviceSupported() != DeviceSupportedResult.Supported #if UNITY_EDITOR_WIN || SystemInfo.graphicsDeviceType != GraphicsDeviceType.Direct3D11 #endif ) { return false; } #if ENABLE_VR #if UNITY_INPUT_SYSTEM InputLayoutLoader.RegisterInputLayouts(); #endif OculusSettings settings = GetSettings(); if (settings != null) { NativeMethods.UserDefinedSettings userDefinedSettings; userDefinedSettings.sharedDepthBuffer = (ushort)(settings.SharedDepthBuffer ? 1 : 0); userDefinedSettings.dashSupport = (ushort)(settings.DashSupport ? 1 : 0); userDefinedSettings.stereoRenderingMode = (ushort)settings.GetStereoRenderingMode(); userDefinedSettings.colorSpace = (ushort)((QualitySettings.activeColorSpace == ColorSpace.Linear) ? 1 : 0); userDefinedSettings.lowOverheadMode = (ushort)(settings.LowOverheadMode ? 1 : 0); userDefinedSettings.optimizeBufferDiscards = (ushort)(settings.OptimizeBufferDiscards ? 1 : 0); userDefinedSettings.phaseSync = (ushort)(settings.PhaseSync ? 1 : 0); userDefinedSettings.symmetricProjection = (ushort)(settings.SymmetricProjection ? 1 : 0); userDefinedSettings.subsampledLayout = (ushort)(settings.SubsampledLayout ? 1 : 0); userDefinedSettings.foveatedRenderingMethod = (ushort)settings.FoveatedRenderingMethod; userDefinedSettings.lateLatching = (ushort)(settings.LateLatching ? 1 : 0); userDefinedSettings.lateLatchingDebug = (ushort)(settings.LateLatchingDebug ? 1 : 0); userDefinedSettings.enableTrackingOriginStageMode = (ushort)(settings.EnableTrackingOriginStageMode ? 1 : 0); #if (UNITY_ANDROID && !UNITY_EDITOR) userDefinedSettings.spaceWarp = (ushort)(settings.SpaceWarp ? 1 : 0); userDefinedSettings.depthSubmission = (ushort)(settings.DepthSubmission ? 1 : 0); #else userDefinedSettings.spaceWarp = 0; userDefinedSettings.depthSubmission = 0; #endif NativeMethods.SetUserDefinedSettings(userDefinedSettings); } CreateSubsystem(s_DisplaySubsystemDescriptors, "oculus display"); CreateSubsystem(s_InputSubsystemDescriptors, "oculus input"); if (displaySubsystem == null && inputSubsystem == null) { #if (UNITY_ANDROID && !UNITY_EDITOR) Debug.LogWarning("Unable to start Oculus XR Plugin."); #else Debug.LogWarning("Unable to start Oculus XR Plugin.\n" + "Possible causes include a headset not being attached, or the Oculus runtime is not installed or up to date.\n" + "If you've recently installed or updated the Oculus runtime, you may need to reboot or close Unity and the Unity Hub and try again."); #endif } else if (displaySubsystem == null) { Debug.LogError("Unable to start Oculus XR Plugin. Failed to load display subsystem."); } else if (inputSubsystem == null) { Debug.LogError("Unable to start Oculus XR Plugin. Failed to load input subsystem."); } else { RegisterUpdateCallback.Initialize(); } return displaySubsystem != null && inputSubsystem != null; #else return false; #endif } public override bool Start() { #if ENABLE_VR StartSubsystem(); StartSubsystem(); #endif Development.OverrideDeveloperModeStart(); return true; } public override bool Stop() { #if ENABLE_VR StopSubsystem(); StopSubsystem(); #endif Development.OverrideDeveloperModeStop(); return true; } public override bool Deinitialize() { RegisterUpdateCallback.Deinitialize(); #if ENABLE_VR DestroySubsystem(); DestroySubsystem(); #endif return true; } #if UNITY_EDITOR_WIN [InitializeOnLoadMethod] static void EditorLoadOVRPlugin() { string ovrpPath = ""; // loop over all the native plugin importers and find the OVRPlugin the editor should use var importers = PluginImporter.GetAllImporters(); foreach (var importer in importers) { if (!importer.GetCompatibleWithEditor() || !importer.assetPath.Contains("OVRPlugin")) continue; if (!importer.GetCompatibleWithPlatform(BuildTarget.StandaloneWindows64) || !importer.assetPath.EndsWith(".dll")) continue; ovrpPath = importer.assetPath; if (!importer.GetIsOverridable()) break; } if (ovrpPath == "") { Debug.LogError("Couldn't locate OVRPlugin.dll"); return; } if (!NativeMethods.LoadOVRPlugin(AssetPathToAbsolutePath(ovrpPath))) { Debug.LogError("Failed to load OVRPlugin.dll"); return; } } static string AssetPathToAbsolutePath(string assetPath) { var path = assetPath.Replace('/', Path.DirectorySeparatorChar); if (assetPath.StartsWith("Packages")) { path = String.Join(Path.DirectorySeparatorChar.ToString(), path.Split(Path.DirectorySeparatorChar).Skip(2)); return Path.Combine(UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath).resolvedPath, path); } else { string assetsPath = Application.dataPath; assetsPath = assetsPath.Replace('/', Path.DirectorySeparatorChar); if (assetsPath.EndsWith("Assets")) { assetsPath = assetsPath.Substring(0, assetsPath.LastIndexOf("Assets")); } return Path.Combine(assetsPath, assetPath); } } #elif (UNITY_STANDALONE_WIN && !UNITY_EDITOR) [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] static void RuntimeLoadOVRPlugin() { try { if (!NativeMethods.LoadOVRPlugin("")) Debug.LogError("Failed to load OVRPlugin.dll"); } catch { // handle Windows standalone build with Oculus XR Plugin installed but disabled in loader list. } } #elif (UNITY_ANDROID && !UNITY_EDITOR) [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] static void RuntimeLoadOVRPlugin() { var supported = IsDeviceSupported(); if (supported == DeviceSupportedResult.ExitApplication) { Debug.LogError("\n\nExiting application:\n\nThis .apk was built with the Oculus XR Plugin loader enabled, but is attempting to run on a non-Oculus device.\nTo build for general Android devices, please disable the Oculus XR Plugin before building the Android player.\n\n\n"); Application.Quit(); } if (supported != DeviceSupportedResult.Supported) return; try { if (!NativeMethods.LoadOVRPlugin("")) Debug.LogError("Failed to load libOVRPlugin.so"); } catch { // handle Android standalone build with Oculus XR Plugin installed but disabled in loader list. } } #endif #if UNITY_EDITOR && XR_MGMT_GTE_320 #if !UNITY_2021_2_OR_NEWER private void RemoveVulkanFromAndroidGraphicsAPIs() { // don't need to do anything if auto apis is selected if (PlayerSettings.GetUseDefaultGraphicsAPIs(BuildTarget.Android)) return; GraphicsDeviceType[] oldApis = PlayerSettings.GetGraphicsAPIs(BuildTarget.Android); List newApisList = new List(); bool vulkanRemoved = false; // copy all entries except vulkan foreach (GraphicsDeviceType dev in oldApis) { if (dev == GraphicsDeviceType.Vulkan) { vulkanRemoved = true; continue; } newApisList.Add(dev); } // if we didn't remove Vulkan from the list, no need to do any further processing if (vulkanRemoved == false) return; if (newApisList.Count <= 0) { newApisList.Add(GraphicsDeviceType.OpenGLES3); Debug.LogWarning( "Vulkan is currently experimental on Oculus Quest. It has been removed from your list of Android graphics APIs and replaced with OpenGLES3.\n" + "If you would like to use experimental Quest Vulkan support, you can add it back into the list of graphics APIs in the Player settings."); } else { Debug.LogWarning( "Vulkan is currently experimental on Oculus Quest. It has been removed from your list of Android graphics APIs.\n" + "If you would like to use experimental Quest Vulkan support, you can add it back into the list of graphics APIs in the Player settings."); } PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, newApisList.ToArray()); } #endif // !UNITY_2021_2_OR_NEWER private void SetAndroidMinSdkVersion() { if (PlayerSettings.Android.minSdkVersion < AndroidSdkVersions.AndroidApiLevel23) { Debug.Log("The Android Minimum API Level has been updated to 23 in Player Settings as this is the minimum required for Oculus builds."); PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel23; } } public override void WasAssignedToBuildTarget(BuildTargetGroup buildTargetGroup) { if (buildTargetGroup == BuildTargetGroup.Android) { #if !UNITY_2021_2_OR_NEWER RemoveVulkanFromAndroidGraphicsAPIs(); #endif SetAndroidMinSdkVersion(); } } #endif // UNITY_EDITOR && XR_MGMT_GTE_320 public OculusSettings GetSettings() { OculusSettings settings = null; #if UNITY_EDITOR UnityEditor.EditorBuildSettings.TryGetConfigObject("Unity.XR.Oculus.Settings", out settings); #else settings = OculusSettings.s_Settings; #endif return settings; } #if UNITY_EDITOR public string GetPreInitLibraryName(BuildTarget buildTarget, BuildTargetGroup buildTargetGroup) { return "OculusXRPlugin"; } #endif private bool CheckUnityVersionCompatibility() { #if !UNITY_2021_3_OR_NEWER Debug.LogWarning("This version of the Oculus XR Plugin requires at least Unity 2021.3.4f1.\n" + "Please update to that version or higher of Unity, or use the verified Oculus XR Plugin package for this version of Unity."); return false; #else // verify that 3.1.0+ versions of the package are on compatible versions of Unity var unityVersion = Application.unityVersion; var versionRegex = @"^(?\d+)\.(?\d+)\.(?\d+)(?.).*"; var match = Regex.Match(unityVersion, versionRegex); // if we can't parse the Unity version number, just assume success if (!match.Success) return true; int major = Int32.Parse((match.Groups["major"].Value)); int minor = Int32.Parse((match.Groups["minor"].Value)); int patch = Int32.Parse((match.Groups["patch"].Value)); var type = match.Groups["type"].Value; // 2021.3 requires at least 2021.3.4f1. this is handled by the package.json, but handle here for internal development as well if (major == 2021) { if ((minor < 3) || ((minor == 3) && (patch < 4))) { Debug.LogWarning("This version of the Oculus XR Plugin requires at least Unity 2021.3.4f1.\n" + "Please update to that version or higher of Unity, or use the verified Oculus XR Plugin package for this version of Unity."); return false; } } // 2022.1 requires at least 2022.1.12f1 if ((major == 2022) && (minor == 1) && (patch < 12)) { Debug.LogWarning("This version of the Oculus XR Plugin requires at least Unity 2022.1.12f1.\n" + "Please update to that version or higher of Unity, or use the verified Oculus XR Plugin package for this version of Unity."); return false; } // 2022.2 requires at least 2022.0b1 if ((major == 2022) && (minor == 2) && (patch == 0) && (type == "a")) { Debug.LogWarning("This version of the Oculus XR Plugin requires at least Unity 2022.2.0b1.\n" + "Please update to that version or higher of Unity, or use the verified Oculus XR Plugin package for this version of Unity."); return false; } return true; #endif } } }