mixedreality/com.microsoft.mixedreality..../Editor/Settings/PlatformValidation.cs

752 lines
40 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#if UNITY_EDITOR
using Microsoft.MixedReality.OpenXR.Remoting;
using System.Collections.Generic;
using System.Linq;
using Unity.XR.CoreUtils.Editor;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.XR.Management.Metadata;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine;
using UnityEngine.SpatialTracking;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using static Microsoft.MixedReality.OpenXR.MixedRealityFeaturePlugin;
namespace Microsoft.MixedReality.OpenXR.Editor
{
/// <summary>
/// Provides a menu item for configuring settings according to specified OpenXR devices.
/// </summary>
internal static class PlatformValidation
{
private static readonly IEnumerable<ValidationRuleset> ValidationRulesetsForRuleGeneration = ((ValidationRuleset[]) System.Enum.GetValues(typeof(ValidationRuleset))).Where((ruleset) => ruleset != ValidationRuleset.None);
private const string OpenXRProjectValidationSettingsPath = "Project/XR Plug-in Management/Project Validation";
private const string Win32StandaloneRulesetMenuPath = "Mixed Reality/Project Validation Settings/Win32 Application (Standalone)";
private const string HoloLens2RulesetMenuPath = "Mixed Reality/Project Validation Settings/HoloLens 2 Application (UWP)";
private const string Win32AppRemotingRulesetMenuPath = "Mixed Reality/Project Validation Settings/Win32 Remoting App for HoloLens 2 (Standalone)";
private const string UWPAppRemotingRulesetMenuPath = "Mixed Reality/Project Validation Settings/Win32 Remoting App for HoloLens 2 (UWP)";
private const string DisableValidationRulesetMenuPath = "Mixed Reality/Project Validation Settings/General Validation Rules Only";
private const string PerformanceHelpLinkText = "Click this icon for more info on recommended performance settings for HoloLens 2.";
private const string PerformanceHelpLink = "https://aka.ms/HoloLens2PerfSettings";
private const string SetupHelpLink = "https://aka.ms/HoloLens2OpenXRConfig";
private static readonly string CannotAutoSetupForHL2 = "Could not automatically apply recommended settings for HoloLens 2. " +
$"Please see {SetupHelpLink} for manual set up instructions";
private static readonly string CannotAutoOptimizeForHL2 = "Could not automatically apply recommended settings for HoloLens 2. " +
$"Please see {PerformanceHelpLink} for manual optimization instructions";
private const string AboutValidationRulesetsHelpText = "This rule has been enabled because a specific application type has been targeted for this project. \r\n" +
"To change or disable these additional validation rules, use the top-level menu:\r\n \"Mixed Reality > Project Validation Settings\"";
[InitializeOnLoadMethod]
private static void InitializePlatformValidation()
{
// These rules are generated for every validation ruleset on every platform - e.g. trying to validate HL2 apps on Standalone will redirect users to UWP.
BuildValidator.AddRules(BuildTargetGroup.WSA, GenerateBuildTargetRules(BuildTargetGroup.WSA));
BuildValidator.AddRules(BuildTargetGroup.Standalone, GenerateBuildTargetRules(BuildTargetGroup.Standalone));
// These rules are generated for every validation ruleset, but only on the platforms where each ruleset is supported.
// If a project is on the wrong platform, that needs to be fixed before these rules will show.
BuildValidator.AddRules(BuildTargetGroup.WSA, GenerateOpenXRLoaderRules(BuildTargetGroup.WSA));
BuildValidator.AddRules(BuildTargetGroup.Standalone, GenerateOpenXRLoaderRules(BuildTargetGroup.Standalone));
BuildValidator.AddRules(BuildTargetGroup.WSA, GenerateRequiredFeatureSetsRules(BuildTargetGroup.WSA));
BuildValidator.AddRules(BuildTargetGroup.Standalone, GenerateRequiredFeatureSetsRules(BuildTargetGroup.Standalone));
BuildValidator.AddRules(BuildTargetGroup.WSA, GenerateInitializeXROnStartRules(BuildTargetGroup.WSA));
BuildValidator.AddRules(BuildTargetGroup.Standalone, GenerateInitializeXROnStartRules(BuildTargetGroup.Standalone));
BuildValidator.AddRules(BuildTargetGroup.WSA, GenerateCameraRules());
// These rules are all HL2 specific, and are only enabled with the HL2 validation ruleset while targeting UWP.
BuildValidationRule[] wsaValidationRules = new BuildValidationRule[] { GenerateHL2RealtimeGIRule(), GenerateHL2QualityRule(),
GenerateHL2RenderAndDepthSubmissionModeRule() };
BuildValidator.AddRules(BuildTargetGroup.WSA, wsaValidationRules);
// These deprecation warnings are always enabled.
BuildValidationRule[] androidDeprecationRules = new BuildValidationRule[] { GenerateAndroidHandTrackingRule(), GenerateAndroidMotionControllerRule() };
BuildValidator.AddRules(BuildTargetGroup.Android, androidDeprecationRules);
}
private static void ChangeValidationRuleset(ValidationRuleset validationRuleset)
{
BuildTargetGroup buildTargetGroup = validationRuleset.GetBuildTargetGroup();
BuildTarget buildTarget = validationRuleset.GetBuildTarget();
string scenarioName = validationRuleset.GetScenarioName();
if (buildTargetGroup == BuildTargetGroup.WSA && !BuildPipeline.IsBuildTargetSupported(BuildTargetGroup.WSA, BuildTarget.WSAPlayer))
{
EditorUtility.DisplayDialog("UWP support not found", "The UWP build support is not currently installed. " +
"Please add the Universal Windows Platform Build Support module to your Unity installation.", "OK");
return;
}
BuildTargetGroup previousBuildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
// Make sure to select the correct platform, which also select the correct tab in the validation window.
// NOTE: must do this selected group change before switching build target below
// otherwise this selection change will not function properly in Unity editor.
if (EditorUserBuildSettings.selectedBuildTargetGroup != buildTargetGroup)
{
EditorUserBuildSettings.selectedBuildTargetGroup = buildTargetGroup;
}
if (EditorUserBuildSettings.activeBuildTarget != buildTarget)
{
if (EditorUtility.DisplayDialog("Change build target platform?", $"This project is currently targeting a platform which does not support {scenarioName} apps. " +
$"To build {scenarioName} applications, the build target platform in Build Settings must be {buildTarget}.\n\n" +
$"Click `Continue` to switch the build target platform to {buildTarget} and open the Project Validation window to review other validation messages.",
"Continue", "Cancel"))
{
EditorUserBuildSettings.SwitchActiveBuildTarget(buildTargetGroup, buildTarget);
}
else
{
EditorUserBuildSettings.selectedBuildTargetGroup = previousBuildTargetGroup;
return;
}
}
ValidationSettings.CurrentRuleset = validationRuleset;
ShowProjectValidationSettings();
}
private static void ShowProjectValidationSettings()
{
// Need to call OpenProjectSettings twice since the first call may not properly bring up the requested page
// Possibly due to the generation of XR settings related files on the fly
SettingsService.OpenProjectSettings(OpenXRProjectValidationSettingsPath);
EditorApplication.delayCall += () =>
{
SettingsService.OpenProjectSettings(OpenXRProjectValidationSettingsPath);
};
}
[MenuItem(Win32StandaloneRulesetMenuPath, isValidateFunction: false, priority: 10)]
private static void ChangeValidationRulesetWin32Standalone() =>
ChangeValidationRuleset(ValidationRuleset.Win32Standalone);
[MenuItem(Win32StandaloneRulesetMenuPath, isValidateFunction: true)]
private static bool ChangeValidationRulesetWin32StandaloneValidator() =>
UpdateRulesetMenuItemChecked(Win32StandaloneRulesetMenuPath, ValidationRuleset.Win32Standalone);
[MenuItem(HoloLens2RulesetMenuPath, isValidateFunction: false, priority: 11)]
private static void ChangeValidationRulesetHoloLens2() =>
ChangeValidationRuleset(ValidationRuleset.HoloLens2);
[MenuItem(HoloLens2RulesetMenuPath, isValidateFunction: true)]
private static bool ChangeValidationRulesetHoloLens2Validator() =>
UpdateRulesetMenuItemChecked(HoloLens2RulesetMenuPath, ValidationRuleset.HoloLens2);
[MenuItem(Win32AppRemotingRulesetMenuPath, isValidateFunction: false, priority: 12)]
private static void ChangeValidationRulesetWin32AppRemoting() =>
ChangeValidationRuleset(ValidationRuleset.Win32AppRemoting);
[MenuItem(Win32AppRemotingRulesetMenuPath, isValidateFunction: true)]
private static bool ChangeValidationRulesetWin32AppRemotingValidator() =>
UpdateRulesetMenuItemChecked(Win32AppRemotingRulesetMenuPath, ValidationRuleset.Win32AppRemoting);
[MenuItem(UWPAppRemotingRulesetMenuPath, isValidateFunction: false, priority: 13)]
private static void ChangeValidationRulesetUWPAppRemoting() =>
ChangeValidationRuleset(ValidationRuleset.UWPAppRemoting);
[MenuItem(UWPAppRemotingRulesetMenuPath, isValidateFunction: true)]
private static bool ChangeValidationRulesetUWPAppRemotingValidator() =>
UpdateRulesetMenuItemChecked(UWPAppRemotingRulesetMenuPath, ValidationRuleset.UWPAppRemoting);
// MenuItems with a difference in priority > 10 will have a horizontal line between them in the menu UI.
[MenuItem(DisableValidationRulesetMenuPath, isValidateFunction: false, priority: 25)]
private static void RemoveValidationRulesets() => ValidationSettings.CurrentRuleset = ValidationRuleset.None;
[MenuItem(DisableValidationRulesetMenuPath, isValidateFunction: true)]
private static bool RemoveValidationRulesetsValidator() =>
UpdateRulesetMenuItemChecked(DisableValidationRulesetMenuPath, ValidationRuleset.None);
#region Validation ruleset rules
private static BuildValidationRule[] GenerateBuildTargetRules(BuildTargetGroup buildTargetGroup)
{
List<BuildValidationRule> validationRules = new List<BuildValidationRule>();
foreach(ValidationRuleset validationRuleset in ValidationRulesetsForRuleGeneration)
{
if(validationRuleset.GetBuildTargetGroup() == buildTargetGroup)
{
// No need for a rule on this build target group, since it must already be correct
continue;
}
string scenarioName = validationRuleset.GetScenarioName();
string platformShortName = validationRuleset.GetPlatformShortName();
validationRules.Add(new BuildValidationRule()
{
// The build target should always be enabled if plugin validation has been enabled.
IsRuleEnabled = () => ValidationSettings.CurrentRuleset == validationRuleset,
Category = $"Mixed Reality OpenXR - {scenarioName} Ruleset",
Message = $"The project needs to target the {platformShortName} platform to build {scenarioName} applications.",
CheckPredicate = () => EditorUserBuildSettings.activeBuildTarget == validationRuleset.GetBuildTarget(),
FixIt = () => {
EditorUserBuildSettings.selectedBuildTargetGroup = validationRuleset.GetBuildTargetGroup();
EditorUserBuildSettings.SwitchActiveBuildTarget(validationRuleset.GetBuildTargetGroup(), validationRuleset.GetBuildTarget());
},
FixItMessage = $"Switch the build target to {platformShortName}",
Error = true,
FixItAutomatic = true,
HelpText = AboutValidationRulesetsHelpText
});
}
return validationRules.ToArray();
}
private static BuildValidationRule[] GenerateOpenXRLoaderRules(BuildTargetGroup buildTargetGroup)
{
List<BuildValidationRule> validationRules = new List<BuildValidationRule>();
foreach(ValidationRuleset validationRuleset in ValidationRulesetsForRuleGeneration)
{
if(validationRuleset.GetBuildTargetGroup() != buildTargetGroup)
{
continue;
}
string scenarioName = validationRuleset.GetScenarioName();
string platformShortName = validationRuleset.GetPlatformShortName();
validationRules.Add(new BuildValidationRule()
{
// The OpenXR loader should always be enabled if a validation ruleset has been enabled.
IsRuleEnabled = () => ValidationSettings.CurrentRuleset == validationRuleset,
Category = $"Mixed Reality OpenXR - {scenarioName} Ruleset",
Message = $"For {scenarioName} applications, the OpenXR loader must be enabled for {platformShortName} in XR plugin management settings.",
CheckPredicate = () => XRPackageMetadataStore.IsLoaderAssigned(typeof(OpenXRLoader).FullName, buildTargetGroup),
FixIt = () => EnableOpenXRLoader(buildTargetGroup),
FixItMessage = $"Assign the OpenXR loader for {platformShortName}",
Error = false,
HelpText = AboutValidationRulesetsHelpText
});
}
return validationRules.ToArray();
}
private static BuildValidationRule[] GenerateRequiredFeatureSetsRules(BuildTargetGroup buildTargetGroup)
{
List<BuildValidationRule> validationRules = new List<BuildValidationRule>();
foreach (ValidationRuleset validationRuleset in ValidationRulesetsForRuleGeneration)
{
if (validationRuleset.GetBuildTargetGroup() != buildTargetGroup)
{
continue;
}
string scenarioName = validationRuleset.GetScenarioName();
string platformShortName = validationRuleset.GetPlatformShortName();
System.Type[] featureSets = validationRuleset.GetRequiredFeatureSets();
List<string> featureSetUINames = new List<string>();
List<string> featureSetIds = new List<string>();
GetIdsAndUserNamesForFeatureSets(featureSets, ref featureSetIds, ref featureSetUINames);
validationRules.Add(new BuildValidationRule()
{
// The necessary feature set should always be enabled if a validation ruleset has been enabled.
IsRuleEnabled = () => ValidationSettings.CurrentRuleset == validationRuleset,
Category = $"Mixed Reality OpenXR - {scenarioName} Ruleset",
Message = $"For {scenarioName} apps, the following feature sets must be enabled for {platformShortName} in OpenXR settings: {string.Join(", ", featureSetUINames)}",
CheckPredicate = () => CheckFeatureSets(buildTargetGroup, featureSetIds),
FixIt = () => EnableFeatureSets(buildTargetGroup, featureSetIds),
FixItMessage = $"Enable the following feature sets for {platformShortName}: {string.Join(", ", featureSetUINames)}",
Error = true,
HelpText = AboutValidationRulesetsHelpText
});
System.Type[] notRequiredfeatureSets = validationRuleset.GetNotRequiredFeatureSets();
List<string> notRequiredFeatureSetUINames = new List<string>();
List<string> notRequiredFeatureSetIds = new List<string>();
GetIdsAndUserNamesForFeatureSets(notRequiredfeatureSets, ref notRequiredFeatureSetIds, ref notRequiredFeatureSetUINames);
if (notRequiredFeatureSetIds.Count > 0)
{
validationRules.Add(new BuildValidationRule()
{
// The necessary feature set should always be enabled if a validation ruleset has been enabled.
IsRuleEnabled = () => ValidationSettings.CurrentRuleset == validationRuleset,
Category = $"Mixed Reality OpenXR - {scenarioName} Ruleset",
Message = $"For {scenarioName} apps, the following feature sets must be disabled for {platformShortName} in OpenXR settings: {string.Join(", ", notRequiredFeatureSetUINames)}",
CheckPredicate = () => !CheckFeatureSets(buildTargetGroup, notRequiredFeatureSetIds),
FixIt = () => DisableFeatureSets(buildTargetGroup, notRequiredFeatureSetIds),
FixItMessage = $"Disable the following feature sets for {platformShortName}: {string.Join(", ", notRequiredFeatureSetUINames)}",
Error = true,
HelpText = AboutValidationRulesetsHelpText
});
}
}
return validationRules.ToArray();
}
private static BuildValidationRule[] GenerateInitializeXROnStartRules(BuildTargetGroup buildTargetGroup)
{
List<BuildValidationRule> validationRules = new List<BuildValidationRule>();
foreach (ValidationRuleset validationRuleset in ValidationRulesetsForRuleGeneration)
{
if (validationRuleset.GetBuildTargetGroup() != buildTargetGroup)
{
continue;
}
string scenarioName = validationRuleset.GetScenarioName();
string platformShortName = validationRuleset.GetPlatformShortName();
bool remotingEnabled = validationRuleset.GetRemotingEnabled();
validationRules.Add(new BuildValidationRule()
{
// This rule will run if a validation ruleset has been enabled. If there is no validation ruleset, we fallback to the validator in AppRemotingValidator.cs
IsRuleEnabled = () => ValidationSettings.CurrentRuleset == validationRuleset,
Category = $"Mixed Reality OpenXR - {scenarioName} Ruleset",
Message = $"For {scenarioName} applications, XR initialization should {(remotingEnabled ? "be delayed until a specific IP address is entered" : "not be delayed")}",
CheckPredicate = () =>
{
XRGeneralSettings settings = XRSettingsHelpers.GetOrCreateXRGeneralSettings(buildTargetGroup);
return settings != null && settings.InitManagerOnStart != remotingEnabled;
},
FixIt = () =>
{
XRGeneralSettings settings = XRSettingsHelpers.GetOrCreateXRGeneralSettings(buildTargetGroup);
if (settings != null)
{
settings.InitManagerOnStart = !remotingEnabled;
}
},
FixItMessage = $"{ (remotingEnabled ? "Disable" : "Enable") } XR initialization on startup",
// If remoting is enabled, this must be enabled. If remoting is not enabled, this should likely be disabled, but it's not required.
Error = remotingEnabled,
HelpText = AboutValidationRulesetsHelpText
});
}
return validationRules.ToArray();
}
#endregion
#region HoloLens 2 rules
private static BuildValidationRule GenerateHL2RealtimeGIRule()
{
return new BuildValidationRule()
{
IsRuleEnabled = IsHL2RulesetEnabled,
Category = "Mixed Reality OpenXR - HoloLens 2 Ruleset",
Message = $"Realtime GI has a negative performance impact on HoloLens 2 applications.",
CheckPredicate = () => !Lightmapping.TryGetLightingSettings(out LightingSettings lightingSettings) || !lightingSettings.realtimeGI,
FixIt = () =>
{
if (Lightmapping.TryGetLightingSettings(out LightingSettings lightingSettings))
{
lightingSettings.realtimeGI = false;
EditorUtility.SetDirty(lightingSettings);
}
else
{
Debug.LogError(CannotAutoOptimizeForHL2);
}
},
FixItMessage = $"Disable realtime GI in lighting settings",
Error = false,
HelpText = PerformanceHelpLinkText + "\r\n\r\n" + AboutValidationRulesetsHelpText,
HelpLink = PerformanceHelpLink
};
}
private static BuildValidationRule GenerateHL2QualityRule()
{
return new BuildValidationRule()
{
// Currently this rule doesn't work as Unity use the "default quality" for a platform to determine the quality level to use
// Setting the "current active quality" does not impact the application running on HoloLens 2
IsRuleEnabled = () => false,
Category = "Mixed Reality OpenXR - HoloLens 2 Ruleset",
Message = $"High quality settings have a negative performance impact on HoloLens 2 applications.",
CheckPredicate = () => QualitySettings.GetQualityLevel() == 0,
FixIt = () => QualitySettings.SetQualityLevel(0, true),
FixItMessage = $"Set quality settings to very low",
Error = false,
HelpText = PerformanceHelpLinkText + "\r\n\r\n" + AboutValidationRulesetsHelpText,
HelpLink = PerformanceHelpLink
};
}
private static BuildValidationRule GenerateHL2GPUSkinningRule()
{
return new BuildValidationRule()
{
IsRuleEnabled = IsHL2RulesetEnabled,
Category = "Mixed Reality OpenXR - HoloLens 2 Ruleset",
Message = $"GPU skinning negatively impacts the performance of HoloLens 2 applications.",
CheckPredicate = () => !PlayerSettings.gpuSkinning,
FixIt = () => PlayerSettings.gpuSkinning = false,
FixItMessage = $"Disable GPU skinning",
Error = false,
HelpText = PerformanceHelpLinkText + "\r\n\r\n" + AboutValidationRulesetsHelpText,
HelpLink = PerformanceHelpLink
};
}
private static BuildValidationRule GenerateHL2RenderAndDepthSubmissionModeRule()
{
return new BuildValidationRule()
{
IsRuleEnabled = IsHL2RulesetEnabled,
Category = "Mixed Reality OpenXR - HoloLens 2 Ruleset",
Message = $"Single pass instanced is recommended for render mode and depth 16 bit is recommended for depth submission mode settings.",
CheckPredicate = () =>
{
if (TryGetOpenXRSetting(BuildTargetGroup.WSA, out OpenXRSettings settings))
{
return settings.depthSubmissionMode == OpenXRSettings.DepthSubmissionMode.Depth16Bit && settings.renderMode == OpenXRSettings.RenderMode.SinglePassInstanced;
}
return true;
},
FixIt = () =>
{
if (TryGetOpenXRSetting(BuildTargetGroup.WSA, out OpenXRSettings settings))
{
settings.depthSubmissionMode = OpenXRSettings.DepthSubmissionMode.Depth16Bit;
settings.renderMode = OpenXRSettings.RenderMode.SinglePassInstanced;
EditorUtility.SetDirty(settings);
}
else
{
Debug.LogError(CannotAutoOptimizeForHL2);
}
},
FixItMessage = $"Switch the render mode to single pass instanced and depth submission mode to depth 16 bit",
Error = false,
HelpText = PerformanceHelpLinkText + "\r\n\r\n" + AboutValidationRulesetsHelpText,
HelpLink = PerformanceHelpLink
};
}
#endregion HoloLens 2 rules
#region Camera setup rules
// Guidelines for using a camera which will render output for a HoloLens 2
private static BuildValidationRule[] GenerateCameraRules()
{
return new BuildValidationRule[] { GenerateCameraFlagsRule(ValidationRuleset.HoloLens2), GenerateCameraFlagsRule(ValidationRuleset.UWPAppRemoting),
GenerateCameraBackgroundColorRule(ValidationRuleset.HoloLens2), GenerateCameraBackgroundColorRule(ValidationRuleset.UWPAppRemoting),
GenerateCameraPoseDriverRule(ValidationRuleset.HoloLens2), GenerateCameraPoseDriverRule(ValidationRuleset.UWPAppRemoting) };
}
private static BuildValidationRule GenerateCameraFlagsRule(ValidationRuleset targetRuleset)
{
return new BuildValidationRule()
{
IsRuleEnabled = () => ValidationSettings.CurrentRuleset == targetRuleset,
Category = $"Mixed Reality OpenXR - {targetRuleset.GetScenarioName()} Ruleset (Scene specific)",
Message = $"It is recommended for the main camera to be cleared with a solid color.",
CheckPredicate = () => Camera.main == null || Camera.main.clearFlags == CameraClearFlags.SolidColor,
FixIt = () =>
{
Camera.main.clearFlags = CameraClearFlags.SolidColor;
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
},
FixItMessage = $"Set the main camera's clearFlags to CameraClearFlags.SolidColor",
Error = false,
FixItAutomatic = false,
SceneOnlyValidation = true,
HelpText = AboutValidationRulesetsHelpText
};
}
private static BuildValidationRule GenerateCameraBackgroundColorRule(ValidationRuleset targetRuleset)
{
return new BuildValidationRule()
{
IsRuleEnabled = () => ValidationSettings.CurrentRuleset == targetRuleset,
Category = $"Mixed Reality OpenXR - {targetRuleset.GetScenarioName()} Ruleset (Scene specific)",
Message = $"It is recommended for the main camera to have a clear background color.",
CheckPredicate = () => Camera.main == null || Camera.main.backgroundColor == Color.clear,
FixIt = () =>
{
Camera.main.backgroundColor = Color.clear;
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
},
FixItMessage = $"Set the main camera background color to clear",
Error = false,
FixItAutomatic = false,
SceneOnlyValidation = true,
HelpText = AboutValidationRulesetsHelpText
};
}
private static BuildValidationRule GenerateCameraPoseDriverRule(ValidationRuleset targetRuleset)
{
return new BuildValidationRule()
{
IsRuleEnabled = () => ValidationSettings.CurrentRuleset == targetRuleset,
Category = $"Mixed Reality OpenXR - {targetRuleset.GetScenarioName()} Ruleset (Scene specific)",
Message = $"It is recommended for the main camera to have a PoseDriver component.",
CheckPredicate = () =>
Camera.main == null
|| Camera.main.gameObject.GetComponent<UnityEngine.SpatialTracking.TrackedPoseDriver>() != null
|| Camera.main.gameObject.GetComponent<UnityEngine.InputSystem.XR.TrackedPoseDriver>() != null
#if USE_ARFOUNDATION && !USE_ARFOUNDATION_5_OR_NEWER
|| Camera.main.gameObject.GetComponent<UnityEngine.XR.ARFoundation.ARPoseDriver>() != null
#endif
,
FixIt = () =>
{
if (Camera.main != null
&& !Camera.main.gameObject.GetComponent<UnityEngine.SpatialTracking.TrackedPoseDriver>()
&& !Camera.main.gameObject.GetComponent<UnityEngine.InputSystem.XR.TrackedPoseDriver>()
#if USE_ARFOUNDATION && !USE_ARFOUNDATION_5_OR_NEWER
&& !Camera.main.gameObject.GetComponent<UnityEngine.XR.ARFoundation.ARPoseDriver>()
#endif
)
{
Camera.main.gameObject.AddComponent<UnityEngine.SpatialTracking.TrackedPoseDriver>();
}
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
},
FixItMessage = $"Ensure the main camera has a PoseDriver component",
Error = false,
FixItAutomatic = false,
SceneOnlyValidation = true,
HelpText = AboutValidationRulesetsHelpText
};
}
#endregion
#region Android rules
private static BuildValidationRule GenerateAndroidHandTrackingRule()
{
var handTrackingFeaturePlugins = new System.Type[] { typeof(HandTrackingFeaturePlugin) };
return new BuildValidationRule()
{
IsRuleEnabled = () => true,
Category = "Mixed Reality OpenXR - Android",
Message = $"Hand tracking on Android with the Mixed Reality OpenXR Plugin has been deprecated. " +
"For Android applications using hand tracking, we recommend transitioning to the OpenXR plugins from Unity and Meta.",
CheckPredicate = () => !CheckFeatures(BuildTargetGroup.Android, handTrackingFeaturePlugins),
FixIt = () => SettingsService.OpenProjectSettings("Project/XR Plug-in Management/OpenXR"),
FixItMessage = $"Open Project Settings to disable the Hand Tracking feature for the Android build target.",
FixItAutomatic = false,
Error = false,
HelpText = AboutValidationRulesetsHelpText
};
}
private static BuildValidationRule GenerateAndroidMotionControllerRule()
{
var motionControllerFeaturePlugins = new System.Type[] { typeof(MotionControllerFeaturePlugin) };
return new BuildValidationRule()
{
IsRuleEnabled = () => true,
Category = "Mixed Reality OpenXR - Android",
Message = $"The Motion Controller Model feature plugin from the Mixed Reality OpenXR Plugin has been deprecated on Android. " +
"For Android applications using motion controller models, we recommend transitioning to the OpenXR plugins from Unity and Meta.",
CheckPredicate = () => !CheckFeatures(BuildTargetGroup.Android, motionControllerFeaturePlugins),
FixIt = () => SettingsService.OpenProjectSettings("Project/XR Plug-in Management/OpenXR"),
FixItMessage = $"Open Project Settings to disable the Motion Controller feature for the Android build target.",
FixItAutomatic = false,
Error = false,
HelpText = AboutValidationRulesetsHelpText
};
}
#endregion Android rules
#region Helpers
private static bool UpdateRulesetMenuItemChecked(string menuPath, ValidationRuleset ruleset)
{
Menu.SetChecked(menuPath, ValidationSettings.CurrentRuleset == ruleset);
return true;
}
private static bool IsHL2RulesetEnabled()
{
// Ensure the OpenXR loader is enabled on WSA - otherwise this is a flat UWP app on HL2 that shouldn't be validated with XR rules.
XRGeneralSettings wsaSettings = XRSettingsHelpers.GetOrCreateXRGeneralSettings(BuildTargetGroup.WSA);
if (!wsaSettings.Manager.activeLoaders.Any(loader => loader is UnityEngine.XR.OpenXR.OpenXRLoader))
{
return false;
}
// Ensure the HL2 feature plugin is enabled, with ValidationRuleTarget == HL2 - otherwise these specific rules are not necessary.
MixedRealityFeaturePlugin plugin = BuildProcessorHelpers.GetOpenXRFeature<MixedRealityFeaturePlugin>(BuildTargetGroup.WSA, false);
return plugin != null && plugin.enabled && (ValidationSettings.CurrentRuleset == ValidationRuleset.HoloLens2);
}
private static void EnableOpenXRLoader(BuildTargetGroup targetGroup)
{
if (XRSettingsHelpers.GetOrCreateXRManagerSettings(targetGroup) is XRManagerSettings settings && settings != null
&& XRPackageMetadataStore.AssignLoader(settings, nameof(OpenXRLoader), targetGroup))
{
EditorUtility.SetDirty(settings);
}
else
{
Debug.LogError(CannotAutoSetupForHL2);
}
}
private static bool CheckFeatureSets(BuildTargetGroup targetGroup, List<string> featureIds)
{
if (XRSettingsHelpers.GetOrCreateXRManagerSettings(targetGroup) is XRManagerSettings settings && settings != null)
{
foreach (var featureSet in OpenXRFeatureSetManager.FeatureSetsForBuildTarget(targetGroup))
{
if (featureIds.Contains(featureSet.featureSetId) && !featureSet.isEnabled)
{
return false;
}
}
}
return true;
}
private static void EnableFeatureSets(BuildTargetGroup targetGroup, List<string> featureIds)
{
foreach (var featureSet in OpenXRFeatureSetManager.FeatureSetsForBuildTarget(targetGroup))
{
if (featureIds.Contains(featureSet.featureSetId) && !featureSet.isEnabled)
{
featureSet.isEnabled = true;
}
}
OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets(targetGroup);
}
private static void DisableFeatureSets(BuildTargetGroup targetGroup, List<string> featureIds)
{
foreach (var featureSet in OpenXRFeatureSetManager.FeatureSetsForBuildTarget(targetGroup))
{
if (featureIds.Contains(featureSet.featureSetId) && featureSet.isEnabled)
{
featureSet.isEnabled = false;
}
}
OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets(targetGroup);
}
// This extension method must be defined from within the Editor package, as it depends on the feature set types.
internal static System.Type[] GetRequiredFeatureSets(this ValidationRuleset validationRuleset)
{
switch (validationRuleset)
{
case ValidationRuleset.Win32Standalone:
return new System.Type[] { typeof(WMRFeatureSet) };
case ValidationRuleset.HoloLens2:
return new System.Type[] { typeof(HoloLensFeatureSet) };
case ValidationRuleset.Win32AppRemoting:
return new System.Type[] { typeof(AppRemotingFeatureSet) };
case ValidationRuleset.UWPAppRemoting:
return new System.Type[] { typeof(AppRemotingFeatureSet) };
}
Debug.LogError($"RequiredFeatureSets of ValidationRuleset \"{validationRuleset}\" are not defined.");
return new System.Type[] { };
}
internal static System.Type[] GetNotRequiredFeatureSets(this ValidationRuleset validationRuleset)
{
switch (validationRuleset)
{
case ValidationRuleset.Win32Standalone:
return new System.Type[] { typeof(AppRemotingFeatureSet) };
case ValidationRuleset.HoloLens2:
return new System.Type[] { typeof(AppRemotingFeatureSet) };
case ValidationRuleset.Win32AppRemoting:
return new System.Type[] { };
case ValidationRuleset.UWPAppRemoting:
return new System.Type[] { };
}
Debug.LogError($"RequiredFeatureSets of ValidationRuleset \"{validationRuleset}\" are not defined.");
return new System.Type[] { };
}
private static bool TryGetOpenXRSetting(BuildTargetGroup targetGroup, out OpenXRSettings openXRSettings)
{
if (EditorBuildSettings.TryGetConfigObject(Constants.k_SettingsKey, out Object obj) && obj is IPackageSettings packageSettings
&& packageSettings.GetSettingsForBuildTargetGroup(targetGroup) is OpenXRSettings settings && settings != null)
{
openXRSettings = settings;
return true;
}
else
{
openXRSettings = null;
return false;
}
}
private static bool CheckFeatures(BuildTargetGroup targetGroup, IEnumerable<System.Type> featureTypes)
{
if (TryGetOpenXRSetting(targetGroup, out OpenXRSettings settings))
{
foreach (OpenXRFeature feature in settings.GetFeatures())
{
if (featureTypes.Contains(feature.GetType()) && !feature.enabled)
{
return false;
}
}
}
return true;
}
private static void EnableFeatures(BuildTargetGroup targetGroup, IEnumerable<System.Type> featureTypes)
{
if (TryGetOpenXRSetting(targetGroup, out OpenXRSettings settings))
{
foreach (OpenXRFeature feature in settings.GetFeatures())
{
if (featureTypes.Contains(feature.GetType()) && !feature.enabled)
{
feature.enabled = true;
}
}
EditorUtility.SetDirty(settings);
}
}
private static void GetIdsAndUserNamesForFeatureSets(System.Type[] featureSets, ref List<string> featureSetIds, ref List<string> featureSetUINames)
{
for(int i = 0; i < featureSets.Length; i++) {
var attribute = featureSets[i].GetCustomAttributes(typeof(OpenXRFeatureSetAttribute), true).FirstOrDefault() as OpenXRFeatureSetAttribute;
if (attribute == null)
{
Debug.LogError($"Could not generate Mixed Reality OpenXR feature set validator - feature set attribute not found for {featureSets[i]}");
continue;
}
featureSetIds.Add(attribute.FeatureSetId);
featureSetUINames.Add(attribute.UiName);
}
}
#endregion Helpers
}
}
#endif