1470 lines
56 KiB
C#
1470 lines
56 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using NUnit.Framework;
|
|
using UnityEngine.XR.OpenXR;
|
|
using UnityEngine.XR.OpenXR.Features;
|
|
using UnityEngine.XR.OpenXR.Features.Mock;
|
|
using UnityEngine.TestTools;
|
|
using UnityEngine.XR.OpenXR.NativeTypes;
|
|
using UnityEngine.Diagnostics;
|
|
|
|
namespace UnityEngine.XR.OpenXR.Tests
|
|
{
|
|
internal class OpenXRRuntimeTests : OpenXRLoaderSetup
|
|
{
|
|
[Test]
|
|
public void TestAvailableExtensions()
|
|
{
|
|
// This test verifies that the list of available extensions contains a subset of known extensions.
|
|
// If certain known extensions are removed from the mock this test should reflect that.
|
|
base.InitializeAndStart();
|
|
string[] extensions = OpenXRRuntime.GetAvailableExtensions();
|
|
HashSet<string> extensionsSet = new HashSet<string>(extensions);
|
|
|
|
List<string> expectedExtensions = new List<string>()
|
|
{
|
|
"XR_UNITY_mock_test",
|
|
"XR_UNITY_null_gfx",
|
|
"XR_KHR_visibility_mask",
|
|
"XR_EXT_user_presence",
|
|
"XR_EXT_conformance_automation",
|
|
"XR_KHR_composition_layer_depth",
|
|
"XR_VARJO_quad_views",
|
|
"XR_MSFT_secondary_view_configuration",
|
|
"XR_EXT_eye_gaze_interaction",
|
|
"XR_MSFT_hand_interaction",
|
|
"XR_MSFT_first_person_observer",
|
|
"XR_META_performance_metrics",
|
|
"XR_EXT_performance_settings"
|
|
};
|
|
|
|
foreach (string expectedExtension in expectedExtensions)
|
|
{
|
|
Assert.IsTrue(extensionsSet.Contains(expectedExtension), $"extensionsSet missing \"{expectedExtension}\"");
|
|
}
|
|
|
|
base.StopAndShutdown();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator SystemIdRetrieved()
|
|
{
|
|
bool systemIdReceived = false;
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnSystemChange))
|
|
{
|
|
systemIdReceived = true;
|
|
Assert.AreEqual(2, param);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
|
|
Assert.IsTrue(systemIdReceived);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator SessionBegan()
|
|
{
|
|
bool sessionBegan = false;
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnSessionBegin))
|
|
{
|
|
sessionBegan = true;
|
|
Assert.AreEqual(3, param);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
|
|
Assert.IsTrue(sessionBegan);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator SessionEnded()
|
|
{
|
|
bool sessionStarted = false;
|
|
bool sessionEnded = false;
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
switch (methodName)
|
|
{
|
|
case nameof(OpenXRFeature.OnSessionBegin):
|
|
Assert.IsFalse(sessionStarted);
|
|
sessionStarted = true;
|
|
Assert.AreEqual(3, param);
|
|
break;
|
|
case nameof(OpenXRFeature.OnSessionEnd):
|
|
Assert.IsTrue(sessionStarted);
|
|
Assert.AreEqual(3, param);
|
|
sessionStarted = false;
|
|
sessionEnded = true;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
|
|
const int ITERATION_MAX_COUNT = 10;
|
|
int waitCount = 0;
|
|
while (!sessionStarted && waitCount++ < ITERATION_MAX_COUNT)
|
|
yield return null;
|
|
|
|
Assert.IsTrue(sessionStarted);
|
|
|
|
base.StopAndShutdown();
|
|
|
|
Assert.IsTrue(sessionEnded);
|
|
}
|
|
|
|
[Test]
|
|
public void SessionDestroyed()
|
|
{
|
|
bool sessionDestroyed = false;
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnSessionDestroy))
|
|
{
|
|
sessionDestroyed = true;
|
|
Assert.AreEqual(3, param);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
base.StopAndShutdown();
|
|
|
|
Assert.IsTrue(sessionDestroyed);
|
|
}
|
|
|
|
[Test]
|
|
public void InstanceDestroyed()
|
|
{
|
|
object instance = null;
|
|
bool instanceDestroyed = false;
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnInstanceCreate))
|
|
{
|
|
instance = param;
|
|
}
|
|
|
|
if (methodName == nameof(OpenXRFeature.OnInstanceDestroy))
|
|
{
|
|
instanceDestroyed = true;
|
|
Assert.AreEqual(instance, param);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
base.StopAndShutdown();
|
|
|
|
Assert.IsTrue(instanceDestroyed);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator XrSpaceApp()
|
|
{
|
|
bool spaceAppSet = false;
|
|
bool spaceAppRemoved = false;
|
|
ulong oldSpaceApp = 0;
|
|
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
// this function checks to see if the initial SetAppSpace call
|
|
// from unity_session.cpp. if you change the default setup in unity_session.cpp
|
|
// you will need to update the value here so that the handle matches.
|
|
// this also makes an assumption that the 3rd space we create is the "Stage"
|
|
// space and that the handles are deterministic.
|
|
if (methodName == nameof(OpenXRFeature.OnAppSpaceChange))
|
|
{
|
|
spaceAppSet = (oldSpaceApp == 0 && (ulong)param == 3);
|
|
spaceAppRemoved = (oldSpaceApp == 3 && (ulong)param == 0);
|
|
oldSpaceApp = (ulong)param;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
|
|
Assert.IsTrue(spaceAppSet);
|
|
Assert.IsFalse(spaceAppRemoved);
|
|
|
|
base.StopAndShutdown();
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator RuntimeName()
|
|
{
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
Assert.AreEqual(OpenXRRuntime.name, "Unity Mock Runtime");
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ExtensionCallbackOrder()
|
|
{
|
|
var callbackQueue = new List<string>();
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
// xrSessionStateChanged is called multiple times, we won't validate it here.
|
|
if (methodName != nameof(OpenXRFeature.OnSessionStateChange))
|
|
callbackQueue.Add(methodName);
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
base.StopAndShutdown();
|
|
yield return null;
|
|
|
|
var expectedCallbackOrder = new List<string>()
|
|
{
|
|
#if UNITY_EDITOR
|
|
nameof(OpenXRFeature.GetValidationChecks),
|
|
#endif
|
|
nameof(OpenXRFeature.HookGetInstanceProcAddr),
|
|
nameof(OpenXRFeature.OnInstanceCreate),
|
|
nameof(OpenXRFeature.OnSystemChange),
|
|
nameof(OpenXRFeature.OnSubsystemCreate),
|
|
nameof(OpenXRFeature.OnSessionCreate),
|
|
nameof(OpenXRFeature.OnFormFactorChange),
|
|
nameof(OpenXRFeature.OnEnvironmentBlendModeChange),
|
|
nameof(OpenXRFeature.OnViewConfigurationTypeChange),
|
|
nameof(OpenXRFeature.OnSessionBegin),
|
|
nameof(OpenXRFeature.OnAppSpaceChange),
|
|
nameof(OpenXRFeature.OnSubsystemStart),
|
|
nameof(OpenXRFeature.OnSubsystemStop),
|
|
nameof(OpenXRFeature.OnSessionEnd),
|
|
nameof(OpenXRFeature.OnSessionExiting),
|
|
nameof(OpenXRFeature.OnSubsystemDestroy),
|
|
nameof(OpenXRFeature.OnSessionDestroy),
|
|
nameof(OpenXRFeature.OnInstanceDestroy)
|
|
};
|
|
|
|
Assert.AreEqual(expectedCallbackOrder, callbackQueue);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator TestConsistentFeatureValues()
|
|
{
|
|
HashSet<string> methodsUsingSession = new HashSet<string>()
|
|
{
|
|
"OnSessionCreate",
|
|
"OnSessionBegin",
|
|
"OnSessionEnd",
|
|
"OnSessionDestroy",
|
|
"OnSessionLossPending",
|
|
"OnSessionExiting"
|
|
};
|
|
|
|
HashSet<string> methodsUsingInstance = new HashSet<string>()
|
|
{
|
|
"OnInstanceCreate",
|
|
"OnInstanceDestroy"
|
|
};
|
|
|
|
Dictionary<string, ulong> methodToSessionValue = new Dictionary<string, ulong>();
|
|
Dictionary<string, ulong> methodToInstanceValue = new Dictionary<string, ulong>();
|
|
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodsUsingSession.Contains(methodName))
|
|
{
|
|
Assert.IsFalse(methodToSessionValue.ContainsKey(methodName));
|
|
methodToSessionValue[methodName] = (ulong)param;
|
|
}
|
|
else if (methodsUsingInstance.Contains(methodName))
|
|
{
|
|
Assert.IsFalse(methodToInstanceValue.ContainsKey(methodName));
|
|
methodToInstanceValue[methodName] = (ulong)param;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
base.StopAndShutdown();
|
|
yield return null;
|
|
|
|
ulong? sessionValue = null;
|
|
ulong? instanceValue = null;
|
|
|
|
foreach (var pair in methodToSessionValue)
|
|
{
|
|
if (sessionValue.HasValue)
|
|
{
|
|
Assert.AreEqual(sessionValue, pair.Value);
|
|
}
|
|
else
|
|
{
|
|
sessionValue = pair.Value;
|
|
}
|
|
}
|
|
|
|
foreach (var pair in methodToInstanceValue)
|
|
{
|
|
if (instanceValue.HasValue)
|
|
{
|
|
Assert.AreEqual(instanceValue, pair.Value);
|
|
}
|
|
else
|
|
{
|
|
instanceValue = pair.Value;
|
|
}
|
|
}
|
|
|
|
Assert.IsTrue(sessionValue.HasValue);
|
|
Assert.IsTrue(instanceValue.HasValue);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator XrSessionStateChanged()
|
|
{
|
|
var states = new List<XrSessionState>();
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnSessionStateChange))
|
|
{
|
|
var oldState = (XrSessionState)((MockRuntime.XrSessionStateChangedParams)param).OldState;
|
|
var newState = (XrSessionState)((MockRuntime.XrSessionStateChangedParams)param).NewState;
|
|
CheckValidStateTransition(oldState, newState);
|
|
states.Add(newState);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
Assert.AreEqual(XrSessionState.Unknown, MockRuntime.sessionState);
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
Assert.AreEqual(XrSessionState.Focused, MockRuntime.sessionState);
|
|
base.StopAndShutdown();
|
|
yield return null;
|
|
Assert.AreEqual(XrSessionState.Unknown, MockRuntime.sessionState);
|
|
|
|
var expected = new List<XrSessionState>()
|
|
{
|
|
XrSessionState.Idle,
|
|
XrSessionState.Ready,
|
|
XrSessionState.Synchronized,
|
|
XrSessionState.Visible,
|
|
XrSessionState.Focused,
|
|
XrSessionState.Visible,
|
|
XrSessionState.Synchronized,
|
|
XrSessionState.Stopping,
|
|
XrSessionState.Idle,
|
|
XrSessionState.Exiting,
|
|
};
|
|
|
|
Assert.AreEqual(states, expected);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator EnableSpecExtension()
|
|
{
|
|
AddExtension(MockRuntime.XR_UNITY_mock_test);
|
|
|
|
base.InitializeAndStart();
|
|
|
|
yield return null;
|
|
|
|
Assert.AreEqual(10, MockRuntime.Instance.XrInstance);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator CheckSpecExtensionVersion()
|
|
{
|
|
AddExtension(MockRuntime.XR_UNITY_mock_test);
|
|
|
|
base.InitializeAndStart();
|
|
|
|
yield return null;
|
|
|
|
Assert.AreEqual(123, OpenXRRuntime.GetExtensionVersion(MockRuntime.XR_UNITY_mock_test));
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator CheckSpecExtensionEnabled()
|
|
{
|
|
AddExtension(MockRuntime.XR_UNITY_mock_test);
|
|
|
|
base.InitializeAndStart();
|
|
|
|
yield return null;
|
|
|
|
Assert.AreEqual(true, OpenXRRuntime.IsExtensionEnabled(MockRuntime.XR_UNITY_mock_test));
|
|
}
|
|
|
|
static OpenXRSettings.DepthSubmissionMode[] depthModes = new OpenXRSettings.DepthSubmissionMode[]
|
|
{
|
|
OpenXRSettings.DepthSubmissionMode.None,
|
|
OpenXRSettings.DepthSubmissionMode.Depth16Bit,
|
|
OpenXRSettings.DepthSubmissionMode.Depth24Bit
|
|
};
|
|
|
|
[UnityTest]
|
|
[UnityPlatform(exclude = new[] { RuntimePlatform.Android })] // Vulkan doesn't have depth on earlier versions of unity
|
|
public IEnumerator CheckDepthSubmissionMode([ValueSource("depthModes")] OpenXRSettings.DepthSubmissionMode depthMode)
|
|
{
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
OpenXRSettings.Instance.depthSubmissionMode = depthMode;
|
|
yield return null;
|
|
Assert.AreEqual(depthMode, OpenXRSettings.Instance.depthSubmissionMode);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator CheckRenderMode()
|
|
{
|
|
base.InitializeAndStart();
|
|
|
|
yield return null;
|
|
|
|
OpenXRSettings.Instance.renderMode = OpenXRSettings.RenderMode.SinglePassInstanced;
|
|
yield return null;
|
|
Assert.AreEqual(OpenXRSettings.Instance.renderMode, OpenXRSettings.RenderMode.SinglePassInstanced);
|
|
|
|
OpenXRSettings.Instance.renderMode = OpenXRSettings.RenderMode.MultiPass;
|
|
yield return null;
|
|
Assert.AreEqual(OpenXRSettings.Instance.renderMode, OpenXRSettings.RenderMode.MultiPass);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator CheckSpecExtensionEnabledAtXrInstanceCreated()
|
|
{
|
|
AddExtension(MockRuntime.XR_UNITY_mock_test);
|
|
|
|
bool xrCreateInstanceCalled = false;
|
|
bool containsMockExt = false;
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnInstanceCreate))
|
|
{
|
|
containsMockExt = OpenXRRuntime.IsExtensionEnabled(MockRuntime.XR_UNITY_mock_test);
|
|
xrCreateInstanceCalled = true;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
Assert.IsTrue(xrCreateInstanceCalled);
|
|
Assert.IsTrue(containsMockExt);
|
|
}
|
|
|
|
/// <summary>
|
|
/// List of extensions to test against runtime loader version greater than 1.1
|
|
/// </summary>
|
|
protected static readonly (string extName, bool expected)[] s_ExtensionsEnableExamples1_1 =
|
|
{
|
|
("XR_EXT_palm_pose", true), //not available for mockruntime, but promoted to core in 1.1 loader, so expect enabled as default.
|
|
("XR_EXT_hp_mixed_reality_controller", true), //available for mockruntime, also promoted to core in 1.1 loader, so expect enabled as default.
|
|
("XR_VARJO_quad_views", true), //available for mockruntime, also promoted to core in 1.1 loader, so expect enabled as default.
|
|
("XR_EXT_local_floor", true), //not available for mockruntime, but promoted to core in 1.1 loader, so expect enabled as default.
|
|
("XR_KHR_composition_layer_cylinder", false), // not available for mockruntime, so expect not enabled.
|
|
("XR_EXT_eye_gaze_interaction", true), // available for mockruntime, so expect enabled.
|
|
("XR_KHR_maintenance1", true), // not available for mockruntime, but promoted to core in 1.1 loader, so expect enabled as default.
|
|
};
|
|
|
|
[UnityTest]
|
|
public IEnumerator CheckExtensionEnabledRuntimeAPIVersion1_1([ValueSource(nameof(s_ExtensionsEnableExamples1_1))] (string extName, bool expected) extension)
|
|
{
|
|
AddExtension(extension.extName);
|
|
|
|
bool xrCreateInstanceCalled = false;
|
|
bool extensionEnabled = false;
|
|
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnInstanceCreate))
|
|
{
|
|
xrCreateInstanceCalled = true;
|
|
extensionEnabled = OpenXRRuntime.IsExtensionEnabled(extension.extName);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
Assert.IsTrue(xrCreateInstanceCalled);
|
|
Assert.IsTrue(extensionEnabled == extension.expected);
|
|
}
|
|
|
|
/// <summary>
|
|
/// List of extensions to test against runtime loader version 1.0
|
|
/// </summary>
|
|
protected static readonly (string extName, bool expected)[] s_ExtensionsEnableExamples1_0 =
|
|
{
|
|
("XR_EXT_palm_pose", false), //not available for mockruntime, so expect not enabled.
|
|
("XR_EXT_hp_mixed_reality_controller", true), //available for mockruntime, so expect enabled.
|
|
("XR_VARJO_quad_views", true), //available for mockruntime, so expect enabled as default.
|
|
("XR_EXT_local_floor", false), //not available for mockruntime, , so expect not enabled.
|
|
("XR_KHR_composition_layer_cylinder", false), // not available for mockruntime, so expect not enabled.
|
|
("XR_EXT_eye_gaze_interaction", true), // available for mockruntime, so expect enabled.
|
|
("XR_KHR_maintenance1", false), // not available for mockruntime, so expect not enabled.
|
|
};
|
|
|
|
[UnityTest]
|
|
public IEnumerator CheckExtensionEnabledRuntimeAPIVersion1_0([ValueSource(nameof(s_ExtensionsEnableExamples1_0))] (string extName, bool expected) extension)
|
|
{
|
|
AddExtension(extension.extName);
|
|
|
|
bool xrCreateInstanceCalled = false;
|
|
bool extensionEnabled = false;
|
|
int attemptCount = 0;
|
|
MockRuntime.SetFunctionCallback("xrCreateInstance", (name) =>
|
|
{
|
|
attemptCount += 1;
|
|
if (attemptCount <= 1)
|
|
{
|
|
return XrResult.ApiVersionUnsupported;
|
|
}
|
|
else
|
|
{
|
|
return XrResult.Success;
|
|
}
|
|
});
|
|
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnInstanceCreate))
|
|
{
|
|
xrCreateInstanceCalled = true;
|
|
extensionEnabled = OpenXRRuntime.IsExtensionEnabled(extension.extName);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
Assert.IsTrue(xrCreateInstanceCalled);
|
|
Assert.IsTrue(extensionEnabled == extension.expected);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator SimulatePause()
|
|
{
|
|
// Initialize and make sure the frame loop is running
|
|
InitializeAndStart();
|
|
yield return new WaitForXrFrame();
|
|
|
|
// Pause will stop the loaders directly
|
|
loader.displaySubsystem.Stop();
|
|
loader.inputSubsystem.Stop();
|
|
|
|
yield return null;
|
|
|
|
// Runtime will transition to idle
|
|
MockRuntime.TransitionToState(XrSessionState.Visible, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Synchronized, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Stopping, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Idle, false);
|
|
yield return null;
|
|
|
|
yield return null;
|
|
|
|
// Unpause will start the loaders directly
|
|
loader.displaySubsystem.Start();
|
|
loader.inputSubsystem.Start();
|
|
|
|
yield return null;
|
|
|
|
// And then transition to ready
|
|
MockRuntime.TransitionToState(XrSessionState.Ready, false);
|
|
yield return new WaitForXrFrame();
|
|
}
|
|
|
|
void DisableHandInteraction()
|
|
{
|
|
foreach (var ext in OpenXRSettings.Instance.features)
|
|
{
|
|
if (ext.nameUi == "Hand Interaction Profile")
|
|
{
|
|
ext.enabled = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Category("HMD")]
|
|
[UnityTest]
|
|
public IEnumerator UserPresence()
|
|
{
|
|
AddExtension("XR_EXT_user_presence");
|
|
|
|
List<InputDevice> hmdDevices = new List<InputDevice>();
|
|
InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.HeadMounted, hmdDevices);
|
|
Assert.That(hmdDevices.Count == 0, Is.True);
|
|
|
|
InitializeAndStart();
|
|
// Wait two frames to let the input catch up with the renderer
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.HeadMounted, hmdDevices);
|
|
Assert.That(hmdDevices.Count > 0, Is.True);
|
|
|
|
bool hasValue = hmdDevices[0].TryGetFeatureValue(CommonUsages.userPresence, out bool isUserPresent);
|
|
Assert.That(hasValue, Is.True);
|
|
Assert.That(isUserPresent, Is.True);
|
|
|
|
//mock no user present
|
|
bool hasUserPresent = false;
|
|
MockRuntime.CauseUserPresenceChange(hasUserPresent);
|
|
yield return new WaitForXrFrame(2);
|
|
hasValue = hmdDevices[0].TryGetFeatureValue(CommonUsages.userPresence, out isUserPresent);
|
|
Assert.That(hasValue, Is.True);
|
|
Assert.That(isUserPresent, Is.False);
|
|
|
|
//mock has user present
|
|
hasUserPresent = true;
|
|
MockRuntime.CauseUserPresenceChange(hasUserPresent);
|
|
yield return new WaitForXrFrame(2);
|
|
hasValue = hmdDevices[0].TryGetFeatureValue(CommonUsages.userPresence, out isUserPresent);
|
|
Assert.That(hasValue, Is.True);
|
|
Assert.That(isUserPresent, Is.True);
|
|
|
|
}
|
|
|
|
#if ENABLE_VR
|
|
[UnityTest]
|
|
public IEnumerator RefreshRate()
|
|
{
|
|
Assert.AreEqual(0.0f, XRDevice.refreshRate);
|
|
base.InitializeAndStart();
|
|
|
|
yield return null;
|
|
// TODO: 19.4 has an additional frame of latency until fix is backported.
|
|
yield return null;
|
|
|
|
Assert.That(XRDevice.refreshRate, Is.EqualTo(60.0f).Within(0.01f));
|
|
}
|
|
|
|
#endif
|
|
|
|
[UnityTest]
|
|
[UnityPlatform(RuntimePlatform.WindowsEditor, RuntimePlatform.WindowsPlayer)]
|
|
public IEnumerator PreInitRealGfxAPI()
|
|
{
|
|
// remove the null gfx device from requested extensions
|
|
MockRuntime.Instance.openxrExtensionStrings = "";
|
|
|
|
bool initedRealGfxApi = false;
|
|
MockRuntime.Instance.TestCallback = (s, o) =>
|
|
{
|
|
if (s == nameof(OpenXRFeature.OnInstanceCreate))
|
|
{
|
|
initedRealGfxApi = new[]
|
|
{
|
|
"XR_KHR_D3D11_enable",
|
|
"XR_KHR_D3D12_enable",
|
|
"XR_KHR_opengl_enable",
|
|
"XR_KHR_opengl_es_enable",
|
|
"XR_KHR_vulkan_enable",
|
|
"XR_KHR_vulkan_enable2",
|
|
}.Any(OpenXRRuntime.IsExtensionEnabled);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
|
|
Assert.That(initedRealGfxApi, Is.True);
|
|
}
|
|
|
|
[UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // OSX doesn't support single-pass very well, disable for test.
|
|
[UnityTest]
|
|
public IEnumerator CombinedFrustum()
|
|
{
|
|
var cameraGO = new GameObject("Test Cam");
|
|
var camera = cameraGO.AddComponent<Camera>();
|
|
|
|
#if UNITY_ANDROID
|
|
if (!SystemInfo.supportsMultiview)
|
|
LogAssert.ignoreFailingMessages = true;
|
|
#endif
|
|
|
|
base.InitializeAndStart();
|
|
OpenXRSettings.Instance.renderMode = OpenXRSettings.RenderMode.SinglePassInstanced;
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
var displays = new List<XRDisplaySubsystem>();
|
|
SubsystemManager.GetSubsystems(displays);
|
|
|
|
Assert.That(displays.Count, Is.EqualTo(1));
|
|
|
|
Assert.That(displays[0].GetRenderPassCount(), Is.EqualTo(1));
|
|
|
|
displays[0].GetRenderPass(0, out var renderPass);
|
|
|
|
renderPass.GetRenderParameter(camera, 0, out var renderParam0);
|
|
renderPass.GetRenderParameter(camera, 1, out var renderParam1);
|
|
displays[0].GetCullingParameters(camera, renderPass.cullingPassIndex, out var cullingParams);
|
|
|
|
// no sense in re-implementing the combining logic here, just the fact they're different shows that we're not using left eye or right eye for culling.
|
|
Assert.That(cullingParams.stereoViewMatrix, Is.Not.EqualTo(renderParam0.view));
|
|
Assert.That(cullingParams.stereoProjectionMatrix, Is.Not.EqualTo(renderParam0.projection));
|
|
|
|
Assert.That(cullingParams.stereoViewMatrix, Is.Not.EqualTo(renderParam1.view));
|
|
Assert.That(cullingParams.stereoProjectionMatrix, Is.Not.EqualTo(renderParam0.projection));
|
|
|
|
Object.Destroy(cameraGO);
|
|
LogAssert.ignoreFailingMessages = false;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator InvalidLocateSpace()
|
|
{
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
switch (methodName)
|
|
{
|
|
case nameof(OpenXRFeature.OnInstanceCreate):
|
|
// Set the location space to invalid data
|
|
MockRuntime.SetSpace(XrReferenceSpaceType.View, Vector3.zero, Quaternion.identity, XrSpaceLocationFlags.None);
|
|
MockRuntime.SetViewState(XrViewConfigurationType.PrimaryStereo, XrViewStateFlags.None);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
base.InitializeAndStart();
|
|
|
|
// Wait a few frames to let the input catch up with the renderer
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.GetEndFrameStats(out var primaryLayerCount, out var secondaryLayerCount);
|
|
Assert.IsTrue(primaryLayerCount == 0);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator FirstPersonObserver()
|
|
{
|
|
AddExtension("XR_MSFT_secondary_view_configuration");
|
|
AddExtension("XR_MSFT_first_person_observer");
|
|
base.InitializeAndStart();
|
|
|
|
MockRuntime.ActivateSecondaryView(XrViewConfigurationType.SecondaryMonoFirstPersonObserver, true);
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.GetEndFrameStats(out var primaryLayerCount, out var secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 1);
|
|
|
|
MockRuntime.ActivateSecondaryView(XrViewConfigurationType.SecondaryMonoFirstPersonObserver, false);
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.GetEndFrameStats(out primaryLayerCount, out secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 0);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator FirstPersonObserverInvalidSecondaryView()
|
|
{
|
|
AddExtension("XR_MSFT_secondary_view_configuration");
|
|
AddExtension("XR_MSFT_first_person_observer");
|
|
base.InitializeAndStart();
|
|
|
|
MockRuntime.ActivateSecondaryView(XrViewConfigurationType.SecondaryMonoFirstPersonObserver, true);
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.GetEndFrameStats(out var primaryLayerCount, out var secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 1);
|
|
|
|
// make mock runtime return invalid state for xrWaitFrame to make sure we don't crash
|
|
MockRuntime.ActivateSecondaryView(0, false);
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.GetEndFrameStats(out primaryLayerCount, out secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 0);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ThirdPersonObserver()
|
|
{
|
|
AddExtension("XR_MSFT_secondary_view_configuration");
|
|
AddExtension("XR_MSFT_third_person_observer_private");
|
|
base.InitializeAndStart();
|
|
|
|
MockRuntime.ActivateSecondaryView(XrViewConfigurationType.SecondaryMonoThirdPersonObserver, true);
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.GetEndFrameStats(out var primaryLayerCount, out var secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 1);
|
|
|
|
MockRuntime.ActivateSecondaryView(XrViewConfigurationType.SecondaryMonoThirdPersonObserver, false);
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.GetEndFrameStats(out primaryLayerCount, out secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 0);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator FirstPersonObserverRestartWhileActive()
|
|
{
|
|
AddExtension("XR_MSFT_secondary_view_configuration");
|
|
AddExtension("XR_MSFT_first_person_observer");
|
|
base.InitializeAndStart();
|
|
|
|
MockRuntime.ActivateSecondaryView(XrViewConfigurationType.SecondaryMonoFirstPersonObserver, true);
|
|
yield return new WaitForXrFrame(1);
|
|
|
|
MockRuntime.GetEndFrameStats(out var primaryLayerCount, out var secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 1);
|
|
|
|
// Transition to ready, which was causing a crash.
|
|
MockRuntime.TransitionToState(XrSessionState.Visible, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Synchronized, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Stopping, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Idle, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Ready, false);
|
|
yield return null;
|
|
|
|
// Check that secondary layer is still there
|
|
MockRuntime.GetEndFrameStats(out primaryLayerCount, out secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 1);
|
|
|
|
// Transition back to focused
|
|
MockRuntime.TransitionToState(XrSessionState.Synchronized, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Visible, false);
|
|
yield return null;
|
|
MockRuntime.TransitionToState(XrSessionState.Focused, false);
|
|
yield return null;
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
// Verify secondary layer is still up and running
|
|
MockRuntime.GetEndFrameStats(out primaryLayerCount, out secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 1);
|
|
|
|
// Make sure we can turn it off
|
|
MockRuntime.ActivateSecondaryView(XrViewConfigurationType.SecondaryMonoFirstPersonObserver, false);
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.GetEndFrameStats(out primaryLayerCount, out secondaryLayerCount);
|
|
Assert.IsTrue(secondaryLayerCount == 0);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator VarjoQuadViews()
|
|
{
|
|
AddExtension("XR_VARJO_quad_views");
|
|
OpenXRSettings.Instance.renderMode = OpenXRSettings.RenderMode.MultiPass;
|
|
// This is a Runtime 1.0 only test
|
|
int attemptCount = 0;
|
|
MockRuntime.SetFunctionCallback("xrCreateInstance", (name) =>
|
|
{
|
|
attemptCount += 1;
|
|
if (attemptCount <= 1)
|
|
{
|
|
return XrResult.ApiVersionUnsupported;
|
|
}
|
|
else
|
|
{
|
|
return XrResult.Success;
|
|
}
|
|
});
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
yield return null;
|
|
Assert.AreEqual(4, loader.displaySubsystem.GetRenderPassCount());
|
|
|
|
OpenXRSettings.Instance.renderMode = OpenXRSettings.RenderMode.SinglePassInstanced;
|
|
yield return null;
|
|
yield return null;
|
|
Assert.AreEqual(3, loader.displaySubsystem.GetRenderPassCount());
|
|
|
|
base.StopAndShutdown();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator NullFeature()
|
|
{
|
|
// Insert a null entry into the features list
|
|
var features = OpenXRSettings.Instance.features.ToList();
|
|
features.Insert(1, null);
|
|
OpenXRSettings.Instance.features = features.ToArray();
|
|
|
|
base.InitializeAndStart();
|
|
|
|
// Wait two frames to make sure nothing else shakes out
|
|
yield return null;
|
|
yield return null;
|
|
|
|
base.StopAndShutdown();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests whether or not the Initialize method of OpenXRLoader will properly handle an exception being thrown
|
|
/// </summary>
|
|
[Test]
|
|
public void InitializeException()
|
|
{
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
switch (methodName)
|
|
{
|
|
case nameof(OpenXRFeature.HookGetInstanceProcAddr):
|
|
throw new Exception("Testing exception within Initialize");
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
LogAssert.ignoreFailingMessages = true;
|
|
base.InitializeAndStart();
|
|
LogAssert.ignoreFailingMessages = false;
|
|
|
|
// The static instance should not be set if initialize failed
|
|
Assert.IsTrue(OpenXRLoaderBase.Instance == null);
|
|
}
|
|
|
|
[DllImport("UnityOpenXR", EntryPoint = "unity_ext_GetRegenerateTrackingOriginFlag")]
|
|
[return: MarshalAs(UnmanagedType.U1)]
|
|
internal static extern bool GetRegenerateTrackingOriginFlag();
|
|
|
|
[UnityTest]
|
|
public IEnumerator RegenerateTrackingOriginFlagTest()
|
|
{
|
|
// First, make sure that LocalFloor is being used in place of Floor (so that the LocalFloor code is triggered)
|
|
OpenXRSettings.SetAllowRecentering(true);
|
|
|
|
// This is a Runtime 1.0 only test - It depends on XR_EXT_local_floor not being active, which only happens for Runtime 1.0
|
|
int attemptCount = 0;
|
|
MockRuntime.SetFunctionCallback("xrCreateInstance", (name) =>
|
|
{
|
|
attemptCount += 1;
|
|
if (attemptCount <= 1)
|
|
{
|
|
return XrResult.ApiVersionUnsupported;
|
|
}
|
|
else
|
|
{
|
|
return XrResult.Success;
|
|
}
|
|
});
|
|
|
|
base.InitializeAndStart();
|
|
|
|
// Make sure that we're setting the TrackingOrigin to floor (to force the generation of the local floor space at time = 0)
|
|
XRInputSubsystem inputSubsystem = Loader.GetLoadedSubsystem<XRInputSubsystem>();
|
|
inputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Device);
|
|
inputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Floor);
|
|
|
|
// Since time = 0, XR_EXT_local_floor is not active, and LocalFloor is requested, this will trigger a tracking origin regeneration.
|
|
Assert.IsTrue(GetRegenerateTrackingOriginFlag());
|
|
|
|
yield return null;
|
|
|
|
// Advancing several frames will allow the tracking origin regeneration to clear.
|
|
yield return new WaitForTrackingOriginRegeneration();
|
|
|
|
// Check that the tracking origin has been regenerated.
|
|
Assert.IsFalse(GetRegenerateTrackingOriginFlag());
|
|
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator FloorTrackingOriginIsRegenerated()
|
|
{
|
|
List<ulong> spaceSequence = new();
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
if (methodName == nameof(OpenXRFeature.OnAppSpaceChange))
|
|
{
|
|
spaceSequence.Add((ulong)param);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
OpenXRSettings.SetAllowRecentering(false);
|
|
OpenXRSettings.RefreshRecenterSpace();
|
|
|
|
base.InitializeAndStart();
|
|
yield return null;
|
|
|
|
XRInputSubsystem inputSubsystem = Loader.GetLoadedSubsystem<XRInputSubsystem>();
|
|
inputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Floor);
|
|
yield return null;
|
|
|
|
OpenXRSettings.SetAllowRecentering(false);
|
|
OpenXRSettings.RefreshRecenterSpace();
|
|
bool regenFlagSet = GetRegenerateTrackingOriginFlag();
|
|
yield return null;
|
|
yield return new WaitForTrackingOriginRegeneration();
|
|
bool regenFlagProcessed = GetRegenerateTrackingOriginFlag();
|
|
|
|
base.StopAndShutdown();
|
|
|
|
Assert.IsTrue(regenFlagSet, "Failed to set regeneration flag");
|
|
Assert.IsFalse(regenFlagProcessed, "Regeneration flag was not processed");
|
|
|
|
var distinctCount = spaceSequence.Distinct().ToList().Count;
|
|
Assert.IsTrue(spaceSequence.Count == distinctCount, "Some XR Space handles didn't change");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simulates what can happen when trying to reconnect Link mode after a link disconnect, but
|
|
/// the headset is reporting a form factor unavailable error.
|
|
/// In this case, the app has already been started, and the runtime reports a form factor unavailable
|
|
/// error when trying to initialize xr again. What should happen is a restart loop to restart XR.
|
|
/// </summary>
|
|
[UnityTest]
|
|
public IEnumerator RestartLoopTest()
|
|
{
|
|
float initialTimeBetweenRestarts = OpenXRRestarter.TimeBetweenRestartAttempts;
|
|
bool initialKeepFunctionCallbacks = MockRuntime.KeepFunctionCallbacks;
|
|
var initialXRGetSystemCallback = MockRuntime.GetBeforeFunctionCallback("xrGetSystem");
|
|
try
|
|
{
|
|
MockRuntime.KeepFunctionCallbacks = true;
|
|
float timeBetweenRestarts = 0.5f;
|
|
|
|
yield return null;
|
|
|
|
// Reduce the time between restarts to reduce the time of this test.
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = timeBetweenRestarts;
|
|
|
|
int resetAttempts = 0;
|
|
MockRuntime.SetFunctionCallback("xrGetSystem", (name) =>
|
|
{
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(true);
|
|
MockRuntime.KeepFunctionCallbacks = true;
|
|
resetAttempts += 1;
|
|
if (resetAttempts <= 2)
|
|
{
|
|
return XrResult.FormFactorUnavailable;
|
|
}
|
|
else
|
|
{
|
|
return XrResult.Success;
|
|
}
|
|
});
|
|
|
|
// Trigger initialize, which should throw an error from xrGetSystem,
|
|
// This will trigger a restart, which should trigger another error from xrGetSystem,
|
|
// Which should trigger another restart, etc. until xrGetSystem returns a success.
|
|
LogAssert.ignoreFailingMessages = true;
|
|
base.InitializeAndStart();
|
|
|
|
yield return new WaitForLoaderRestart(10, true);
|
|
Assert.AreEqual(3, resetAttempts);
|
|
}
|
|
finally
|
|
{
|
|
MockRuntime.KeepFunctionCallbacks = initialKeepFunctionCallbacks;
|
|
MockRuntime.SetFunctionCallback("xrGetSystem", initialXRGetSystemCallback);
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = initialTimeBetweenRestarts;
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests that OpenXRRuntime.wantsToRestart is a global switch for de-activating the restarting of XR.
|
|
/// </summary>
|
|
[UnityTest]
|
|
public IEnumerator RestartLoopDisabledTest()
|
|
{
|
|
OpenXRRuntime.wantsToRestart += () => false;
|
|
OpenXRRuntime.wantsToQuit += () => true;
|
|
float initialTimeBetweenRestarts = OpenXRRestarter.TimeBetweenRestartAttempts;
|
|
var initialXRGetSystemCallback = MockRuntime.GetBeforeFunctionCallback("xrGetSystem");
|
|
try
|
|
{
|
|
float timeBetweenRestarts = 1.0f;
|
|
|
|
yield return null;
|
|
|
|
// Should have 0 restart attempts before starting.
|
|
Debug.Log("Restart Attempts:" + OpenXRRestarter.PauseAndRestartAttempts.ToString());
|
|
Assert.AreEqual(0, OpenXRRestarter.PauseAndRestartAttempts);
|
|
|
|
// Reduce the time between restarts to reduce the time of this test.
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = timeBetweenRestarts;
|
|
|
|
// Trigger initialize, which should throw the form factor unavailable error.
|
|
MockRuntime.SetFunctionCallback("xrGetSystem", (name) =>
|
|
{
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(true);
|
|
return XrResult.FormFactorUnavailable;
|
|
});
|
|
base.InitializeAndStart();
|
|
|
|
// This retry attempt should not succeed since we manually set wantsToRestart = false.
|
|
yield return new WaitForLoaderShutdown();
|
|
Assert.IsTrue(OpenXRLoader.Instance == null, "OpenXR should not be initialized");
|
|
}
|
|
finally
|
|
{
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = initialTimeBetweenRestarts;
|
|
OpenXRRuntime.wantsToRestart -= () => false;
|
|
OpenXRRuntime.wantsToQuit -= () => true;
|
|
MockRuntime.SetFunctionCallback("xrGetSystem", initialXRGetSystemCallback);
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simulates what happens when trying to initialize xr for the first time, and the headset is disconnected,
|
|
/// causing xrGetSystem to report a form factor unavailable error.
|
|
/// By default, xr initialization should not be retried per feedback from Microsoft.
|
|
/// </summary>
|
|
[UnityTest]
|
|
public IEnumerator RestartLoopDisabledBeforeInitializationTest()
|
|
{
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(false);
|
|
float initialTimeBetweenRestarts = OpenXRRestarter.TimeBetweenRestartAttempts;
|
|
var initialXRGetSystemCallback = MockRuntime.GetBeforeFunctionCallback("xrGetSystem");
|
|
try
|
|
{
|
|
float timeBetweenRestarts = 1.0f;
|
|
|
|
yield return null;
|
|
|
|
// Reduce the time between restarts to reduce the time of this test.
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = timeBetweenRestarts;
|
|
|
|
// When XRGetSystem is called, start listening for a null OpenXRLoader since initialization has
|
|
// already started.
|
|
WaitForNullXRLoader nullLoaderYieldInstruction = new WaitForNullXRLoader();
|
|
MockRuntime.SetFunctionCallback("xrGetSystem", (name) =>
|
|
{
|
|
Debug.Log("Calling XRGetSystem");
|
|
nullLoaderYieldInstruction.StartListening();
|
|
return XrResult.FormFactorUnavailable;
|
|
});
|
|
|
|
// Trigger initialize, which should throw the form factor unavailable error.
|
|
base.InitializeAndStart();
|
|
|
|
yield return nullLoaderYieldInstruction;
|
|
Assert.IsTrue(OpenXRLoader.Instance == null, "OpenXR should not be initialized");
|
|
}
|
|
finally
|
|
{
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = initialTimeBetweenRestarts;
|
|
MockRuntime.SetFunctionCallback("xrGetSystem", initialXRGetSystemCallback);
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simulates what happens when trying to initialize xr for the first time, and the runtime reports
|
|
/// a form factor unavailable error for xrGetSystem. If the user chooses, the runtime can retry initialization again and again
|
|
/// while waiting for the xrGetSystem call to succeed.
|
|
/// By default, xr initialization should not be retried per feedback from Microsoft. This loop needs to be
|
|
/// explicitly enabled.
|
|
/// </summary>
|
|
[UnityTest]
|
|
public IEnumerator XRGetSystemLoopBeforeInitializationTest()
|
|
{
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(false);
|
|
float initialTimeBetweenRestarts = OpenXRRestarter.TimeBetweenRestartAttempts;
|
|
bool initialKeepFunctionCallbacks = MockRuntime.KeepFunctionCallbacks;
|
|
var initialXRGetSystemCallback = MockRuntime.GetBeforeFunctionCallback("xrGetSystem");
|
|
bool initialRetryInitializationOnFormFactorErrors = OpenXRRuntime.retryInitializationOnFormFactorErrors;
|
|
try
|
|
{
|
|
yield return null;
|
|
|
|
// Enable this to prevent xrGetSystem callback override from getting overwritten.
|
|
MockRuntime.KeepFunctionCallbacks = true;
|
|
|
|
// Reduce the time between restarts to reduce the time of this test.
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = 1.0f;
|
|
|
|
// Enable the xrGetSystem retry loop per this test.
|
|
OpenXRRuntime.retryInitializationOnFormFactorErrors = true;
|
|
|
|
// Enable this to ignore the error messages in the logs.
|
|
LogAssert.ignoreFailingMessages = true;
|
|
|
|
int resetAttempts = 0;
|
|
MockRuntime.SetFunctionCallback("xrGetSystem", (name) =>
|
|
{
|
|
MockRuntime.KeepFunctionCallbacks = true;
|
|
|
|
resetAttempts += 1;
|
|
if (resetAttempts <= 2)
|
|
{
|
|
return XrResult.FormFactorUnavailable;
|
|
}
|
|
else
|
|
{
|
|
return XrResult.Success;
|
|
}
|
|
});
|
|
|
|
// Trigger initialize, which should call into xrGetSystem.
|
|
base.InitializeAndStart();
|
|
|
|
yield return new WaitForLoaderRestart(10, true);
|
|
Assert.AreEqual(3, resetAttempts);
|
|
Assert.IsTrue(OpenXRLoader.Instance != null, "OpenXR should be initialized");
|
|
}
|
|
finally
|
|
{
|
|
MockRuntime.KeepFunctionCallbacks = initialKeepFunctionCallbacks;
|
|
OpenXRRuntime.retryInitializationOnFormFactorErrors = initialRetryInitializationOnFormFactorErrors;
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = initialTimeBetweenRestarts;
|
|
MockRuntime.SetFunctionCallback("xrGetSystem", initialXRGetSystemCallback);
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(false);
|
|
}
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator WantsToRestartTrue()
|
|
{
|
|
OpenXRRuntime.wantsToRestart += () => true;
|
|
OpenXRRuntime.wantsToRestart += () => true;
|
|
OpenXRRuntime.wantsToRestart += () => true;
|
|
|
|
InitializeAndStart();
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.TransitionToState(XrSessionState.LossPending, true);
|
|
|
|
yield return new WaitForLoaderRestart();
|
|
yield return new WaitForXrFrame(1);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator WantsToRestartFalse()
|
|
{
|
|
OpenXRRuntime.wantsToRestart += () => true;
|
|
OpenXRRuntime.wantsToRestart += () => false;
|
|
OpenXRRuntime.wantsToRestart += () => true;
|
|
|
|
InitializeAndStart();
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.TransitionToState(XrSessionState.LossPending, true);
|
|
|
|
yield return new WaitForLoaderShutdown();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator WantsToQuitTrue()
|
|
{
|
|
var onQuit = false;
|
|
OpenXRRuntime.wantsToQuit += () => true;
|
|
OpenXRRuntime.wantsToQuit += () => true;
|
|
OpenXRRuntime.wantsToQuit += () => true;
|
|
OpenXRRestarter.Instance.onQuit += () => onQuit = true;
|
|
|
|
InitializeAndStart();
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.CauseInstanceLoss();
|
|
|
|
yield return new WaitForLoaderShutdown();
|
|
|
|
Assert.IsTrue(OpenXRLoader.Instance == null, "OpenXR should not be running");
|
|
Assert.IsTrue(onQuit, "Quit was not called");
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator WantsToQuitFalse()
|
|
{
|
|
var onQuit = false;
|
|
OpenXRRuntime.wantsToQuit += () => true;
|
|
OpenXRRuntime.wantsToQuit += () => false;
|
|
OpenXRRuntime.wantsToQuit += () => true;
|
|
OpenXRRestarter.Instance.onQuit += () => onQuit = true;
|
|
|
|
InitializeAndStart();
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.CauseInstanceLoss();
|
|
|
|
yield return new WaitForLoaderShutdown();
|
|
|
|
Assert.IsTrue(OpenXRLoader.Instance == null, "OpenXR should not be running");
|
|
Assert.IsFalse(onQuit, "Quit was not called");
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator LossPendingCausesRestart()
|
|
{
|
|
bool lossPendingReceived = false;
|
|
MockRuntime.Instance.TestCallback = (methodName, param) =>
|
|
{
|
|
switch (methodName)
|
|
{
|
|
case nameof(OpenXRFeature.OnSessionLossPending):
|
|
lossPendingReceived = true;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
InitializeAndStart();
|
|
|
|
yield return new WaitForXrFrame(1);
|
|
|
|
Assert.IsTrue(MockRuntime.TransitionToState(XrSessionState.LossPending, true), "Failed to transition to loss pending state");
|
|
|
|
yield return new WaitForLoaderRestart();
|
|
|
|
Assert.IsTrue(lossPendingReceived);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator CreateSwapChainRuntimeError()
|
|
{
|
|
MockRuntime.SetFunctionCallback("xrCreateSwapchain", (func) => XrResult.RuntimeFailure);
|
|
|
|
InitializeAndStart();
|
|
|
|
yield return new WaitForLoaderShutdown();
|
|
|
|
Assert.IsTrue(OpenXRLoader.Instance == null, "OpenXR should not be initialized");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simulates what can happen when trying to reconnect Link mode after a link disconnect. During
|
|
/// xr re-initialization, creating the swapchain can result in a session lost error, which should trigger a
|
|
/// loop to attempt to restart xr.
|
|
/// </summary>
|
|
[UnityTest]
|
|
public IEnumerator CreateSwapChainSessionLostError()
|
|
{
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(true);
|
|
float initialTimeBetweenRestarts = OpenXRRestarter.TimeBetweenRestartAttempts;
|
|
var initialXRCreateSwapchainCallback = MockRuntime.GetBeforeFunctionCallback("xrCreateSwapchain");
|
|
try
|
|
{
|
|
float timeBetweenRestarts = 1.0f;
|
|
|
|
// Reduce the time between restarts to reduce the time of this test.
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = timeBetweenRestarts;
|
|
|
|
MockRuntime.SetFunctionCallback("xrCreateSwapchain", (func) => XrResult.SessionLost);
|
|
InitializeAndStart();
|
|
|
|
yield return new WaitForLoaderRestart(10, true);
|
|
|
|
Assert.IsTrue(OpenXRLoader.Instance != null, "OpenXR should be initialized");
|
|
}
|
|
finally
|
|
{
|
|
OpenXRRestarter.TimeBetweenRestartAttempts = initialTimeBetweenRestarts;
|
|
MockRuntime.SetFunctionCallback("xrCreateSwapchain", initialXRCreateSwapchainCallback);
|
|
OpenXRLoaderBase.Internal_SetSuccessfullyInitialized(false);
|
|
}
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator CreateSessionRuntimeFailure()
|
|
{
|
|
MockRuntime.SetFunctionCallback("xrCreateSession", (func) => XrResult.RuntimeFailure);
|
|
|
|
InitializeAndStart();
|
|
|
|
yield return null;
|
|
|
|
Assert.IsTrue(DoesDiagnosticReportContain(new System.Text.RegularExpressions.Regex(@"xrCreateSession: XR_ERROR_RUNTIME_FAILURE")));
|
|
Assert.IsTrue(OpenXRLoader.Instance.currentLoaderState == OpenXRLoaderBase.LoaderState.Stopped, "OpenXR should be stopped");
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator EndFrameRuntimeFailure()
|
|
{
|
|
InitializeAndStart();
|
|
|
|
yield return new WaitForXrFrame(2);
|
|
|
|
MockRuntime.SetFunctionCallback("xrEndFrame", (func) => XrResult.RuntimeFailure);
|
|
|
|
yield return null;
|
|
yield return null;
|
|
yield return null;
|
|
|
|
Assert.IsTrue(DoesDiagnosticReportContain(new System.Text.RegularExpressions.Regex(@"xrEndFrame: XR_ERROR_RUNTIME_FAILURE")));
|
|
Assert.IsTrue(OpenXRLoader.Instance == null, "OpenXR should be shutdown");
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator MultipleRestart()
|
|
{
|
|
InitializeAndStart();
|
|
yield return new WaitForXrFrame();
|
|
|
|
OpenXRRestarter.Instance.ShutdownAndRestart();
|
|
yield return new WaitForXrFrame();
|
|
|
|
OpenXRRestarter.Instance.ShutdownAndRestart();
|
|
yield return new WaitForXrFrame();
|
|
}
|
|
|
|
[UnityTest]
|
|
[UnityPlatform(include = new[] { RuntimePlatform.Android })]
|
|
public IEnumerator AndroidThreadSettingsSetAtInitialization()
|
|
{
|
|
InitializeAndStart();
|
|
|
|
yield return new WaitForXrFrame(1);
|
|
|
|
if (!OpenXRRuntime.IsExtensionEnabled("XR_KHR_android_thread_settings"))
|
|
{
|
|
Assert.Inconclusive("Current XR runtime is not compatible with XR_KHR_android_thread_settings extension");
|
|
}
|
|
|
|
var threadSettingsCount = MockRuntime.GetRegisteredAndroidThreadsCount();
|
|
var mainThreadFound = MockRuntime.IsAndroidThreadTypeRegistered(1); // XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR
|
|
var renderThreadFound = MockRuntime.IsAndroidThreadTypeRegistered(3); // XR_ANDROID_THREAD_TYPE_RENDERER_MAIN_KHR
|
|
|
|
StopAndShutdown();
|
|
|
|
Debug.Log($"threadSettings.Count={threadSettingsCount}");
|
|
Assert.AreEqual(2, threadSettingsCount, "Unexpected number of Android thread settings registered.");
|
|
Assert.IsTrue(mainThreadFound, "Main thread not found in Android thread settings.");
|
|
Assert.IsTrue(renderThreadFound, "Graphics thread not found in Android thread settings.");
|
|
}
|
|
}
|
|
}
|