上传YomovSDK

This commit is contained in:
Sora丶kong
2026-03-03 03:15:46 +08:00
parent 9096da7e6c
commit eb97f31065
6477 changed files with 1932208 additions and 3 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aae4484d37f3dc949851ce06e791456e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,370 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Xml;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEditor.PackageManager;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using VIVE.OpenXR.Hand;
using VIVE.OpenXR.Tracker;
// Reference to Unity's OpenXR package
namespace VIVE.OpenXR.Editor
{
internal class ModifyAndroidManifest : OpenXRFeatureBuildHooks
{
public override int callbackOrder => 1;
public override Type featureType => typeof(VIVEFocus3Feature);
protected override void OnPreprocessBuildExt(BuildReport report)
{
}
private static string _manifestPath;
protected override void OnPostGenerateGradleAndroidProjectExt(string path)
{
_manifestPath = GetManifestPath(path);
var androidManifest = new AndroidManifest(_manifestPath);
//androidManifest.AddVIVECategory();
androidManifest.AddViveSDKVersion();
androidManifest.AddUnityVersion();
androidManifest.AddOpenXRPermission();
androidManifest.AddOpenXRFeatures();
androidManifest.Save();
}
protected override void OnPostprocessBuildExt(BuildReport report)
{
if (File.Exists(_manifestPath))
File.Delete(_manifestPath);
}
private string _manifestFilePath;
private string GetManifestPath(string basePath)
{
if (!string.IsNullOrEmpty(_manifestFilePath)) return _manifestFilePath;
var pathBuilder = new StringBuilder(basePath);
pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
_manifestFilePath = pathBuilder.ToString();
return _manifestFilePath;
}
private class AndroidXmlDocument : XmlDocument
{
private string m_Path;
protected XmlNamespaceManager nsMgr;
public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
public AndroidXmlDocument(string path)
{
m_Path = path;
using (var reader = new XmlTextReader(m_Path))
{
reader.Read();
Load(reader);
}
nsMgr = new XmlNamespaceManager(NameTable);
nsMgr.AddNamespace("android", AndroidXmlNamespace);
}
public string Save()
{
return SaveAs(m_Path);
}
public string SaveAs(string path)
{
using (var writer = new XmlTextWriter(path, new UTF8Encoding(false)))
{
writer.Formatting = Formatting.Indented;
Save(writer);
}
return path;
}
}
[InitializeOnLoad]
public static class CheckIfSimultaneousInteractionEnabled
{
const string LOG_TAG = "CheckIfSimultaneousInteractionEnabled ";
static StringBuilder m_sb = null;
static StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
static void DEBUG(StringBuilder msg) { Debug.Log(msg); }
internal const string MENU_NAME = "VIVE/Interaction Mode/Enable Simultaneous Interaction";
private static bool m_IsEnabled = false;
public static bool IsEnabled { get { return m_IsEnabled; } }
static CheckIfSimultaneousInteractionEnabled()
{
m_IsEnabled = EditorPrefs.GetBool(MENU_NAME, false);
/// Delaying until first editor tick so that the menu
/// will be populated before setting check state, and
/// re-apply correct action
EditorApplication.delayCall += () =>
{
PerformAction(m_IsEnabled);
};
}
[MenuItem(MENU_NAME, priority = 601)]
private static void ToggleAction()
{
/// Toggling action
PerformAction(!m_IsEnabled);
}
public static void PerformAction(bool enabled)
{
/// Set checkmark on menu item
Menu.SetChecked(MENU_NAME, enabled);
/// Saving editor state
EditorPrefs.SetBool(MENU_NAME, enabled);
m_IsEnabled = enabled;
//sb.Clear().Append(LOG_TAG).Append(m_IsEnabled ? "Enable " : "Disable ").Append("Simultaneous Interaction."); DEBUG(sb);
}
[MenuItem(MENU_NAME, validate = true, priority = 601)]
public static bool ValidateEnabled()
{
Menu.SetChecked(MENU_NAME, m_IsEnabled);
return true;
}
}
private class AndroidManifest : AndroidXmlDocument
{
private readonly XmlElement IntetnFilterElement;
private readonly XmlElement ManifestElement;
private readonly XmlElement ApplicationElement;
public AndroidManifest(string path) : base(path)
{
IntetnFilterElement = SelectSingleNode("/manifest/application/activity/intent-filter") as XmlElement;
ManifestElement = SelectSingleNode("/manifest") as XmlElement;
ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
}
private XmlAttribute CreateAndroidAttribute(string key, string value)
{
XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
attr.Value = value;
return attr;
}
private const string FAKE_VERSION = "0.0.0";
private static string SearchPackageVersion(string packageName)
{
var listRequest = Client.List(true);
do
{
if (listRequest.IsCompleted)
{
if (listRequest.Result == null)
{
Debug.Log("List result: is empty");
return FAKE_VERSION;
}
foreach (var pi in listRequest.Result)
{
//Debug.Log("List has: " + pi.name + " == " + packageName);
if (pi.name == packageName)
{
Debug.Log("Found " + packageName);
return pi.version;
}
}
break;
}
Thread.Sleep(100);
} while (true);
return FAKE_VERSION;
}
internal void AddViveSDKVersion()
{
var newUsesFeature = CreateElement("meta-data");
newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "com.htc.ViveOpenXR.SdkVersion"));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("value", SearchPackageVersion("com.htc.upm.vive.openxr")));
ApplicationElement.AppendChild(newUsesFeature);
}
internal void AddUnityVersion()
{
var newUsesFeature = CreateElement("meta-data");
newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "com.htc.vr.content.UnityVersion"));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("value", Application.unityVersion));
ApplicationElement.AppendChild(newUsesFeature);
}
internal void AddVIVECategory()
{
var md = IntetnFilterElement.AppendChild(CreateElement("category"));
md.Attributes.Append(CreateAndroidAttribute("name", "com.htc.intent.category.VRAPP"));
}
internal void AddOpenXRPermission()
{
var md = ManifestElement.AppendChild(CreateElement("uses-permission"));
md.Attributes.Append(CreateAndroidAttribute("name", "org.khronos.openxr.permission.OPENXR"));
md = ManifestElement.AppendChild(CreateElement("uses-permission"));
md.Attributes.Append(CreateAndroidAttribute("name", "org.khronos.openxr.permission.OPENXR_SYSTEM"));
var md2 = IntetnFilterElement.AppendChild(CreateElement("category"));
md2.Attributes.Append(CreateAndroidAttribute("name", "org.khronos.openxr.intent.category.IMMERSIVE_HMD"));
}
internal void AddOpenXRFeatures()
{
bool enableHandtracking = false;
bool enableTracker = false;
bool enableEyetracking = false;
bool enableLipexpression = false;
const string kHandTrackingExtension = "XR_EXT_hand_tracking";
const string kFacialTrackingExtension = "XR_HTC_facial_tracking";
const string kHandInteractionHTC = "XR_HTC_hand_interaction";
const string kHandInteractionEXT = "XR_EXT_hand_interaction";
var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android);
if (null == settings)
return;
foreach (var feature in settings.GetFeatures<OpenXRInteractionFeature>())
{
if ((feature is ViveWristTracker || feature is ViveXRTracker) && feature.enabled)
{
enableHandtracking = true;
enableTracker = true;
}
if (feature is EyeGazeInteraction && feature.enabled)
{
enableEyetracking = true;
}
if (feature is ViveHandInteraction && feature.enabled)
{
enableHandtracking = true;
}
}
var features = settings.GetFeatures<OpenXRFeature>();
foreach (var feature in features)
{
if (!feature.enabled) { continue; }
FieldInfo fieldInfoOpenXrExtensionStrings = typeof(OpenXRFeature).GetField(
"openxrExtensionStrings",
BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfoOpenXrExtensionStrings != null)
{
var openXrExtensionStringsArray =
((string)fieldInfoOpenXrExtensionStrings.GetValue(feature)).Split(' ');
foreach (string stringItem in openXrExtensionStringsArray)
{
if (string.IsNullOrEmpty(stringItem)) { continue; }
if (stringItem.Equals(kHandTrackingExtension) ||
stringItem.Equals(kHandInteractionHTC) ||
stringItem.Equals(kHandInteractionEXT))
{
enableHandtracking = true;
}
if (stringItem.Equals(kFacialTrackingExtension))
{
enableEyetracking = true;
enableLipexpression = true;
}
}
}
if (feature is VIVEFocus3Feature)
{
for (int i = 0; i < features.Length; i++)
{
if (features[i] is Enterprise.ViveEnterpriseCommand)
{
features[i].enabled = true;
}
}
}
}
if (enableHandtracking)
{
var newUsesFeature = CreateElement("uses-feature");
newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.handtracking"));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true"));
ManifestElement.AppendChild(newUsesFeature);
}
if (enableTracker)
{
var newUsesFeature = CreateElement("uses-feature");
newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.tracker"));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true"));
ManifestElement.AppendChild(newUsesFeature);
}
if (enableEyetracking)
{
var newUsesFeature = CreateElement("uses-feature");
newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.eyetracking"));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true"));
ManifestElement.AppendChild(newUsesFeature);
}
if (enableLipexpression)
{
var newUsesFeature = CreateElement("uses-feature");
newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.lipexpression"));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true"));
ManifestElement.AppendChild(newUsesFeature);
}
if (CheckIfSimultaneousInteractionEnabled.IsEnabled)
{
var newUsesFeature = CreateElement("uses-feature");
newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.simultaneous_interaction"));
newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true"));
ManifestElement.AppendChild(newUsesFeature);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cd029ee7a3204f446b0def5eafc0a380
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ddf1278db13678e45b960b274d526b39
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 213dee666ce1d064bac61bf4164523d9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,826 @@
// "VIVE SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the VIVE SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using UnityEngine;
#if UNITY_EDITOR
namespace VIVE.OpenXR.CompositionLayer.Editor
{
#region Composition Layer Editor
using UnityEditor;
using UnityEditor.SceneManagement;
using VIVE.OpenXR.CompositionLayer;
using UnityEditor.XR.OpenXR.Features;
[CustomEditor(typeof(CompositionLayer))]
public class CompositionLayerEditor : Editor
{
static string PropertyName_LayerType = "layerType";
static GUIContent Label_LayerType = new GUIContent("Type", "Specify the type of the composition layer.");
SerializedProperty Property_LayerType;
static string PropertyName_CompositionDepth = "compositionDepth";
static GUIContent Label_CompositionDepth = new GUIContent("Composition Depth", "Specify the composition depth of the layer.");
SerializedProperty Property_CompositionDepth;
static string PropertyName_LayerShape = "layerShape";
static GUIContent Label_LayerShape = new GUIContent("Shape", "Specify the shape of the layer.");
SerializedProperty Property_LayerShape;
static string PropertyName_LayerVisibility = "layerVisibility";
static GUIContent Label_LayerVisibility = new GUIContent("Visibility", "Specify the visibility of the layer.");
SerializedProperty Property_LayerVisibility;
static string PropertyName_LockMode = "lockMode";
static GUIContent Label_LockMode = new GUIContent("Locked Parameter", "Cylinder Layer parameter to be locked when changing the radius.");
SerializedProperty Property_LockMode;
static string PropertyName_QuadWidth = "m_QuadWidth";
static GUIContent Label_QuadWidth = new GUIContent("Width", "Width of a Quad Layer");
SerializedProperty Property_QuadWidth;
static string PropertyName_QuadHeight = "m_QuadHeight";
static GUIContent Label_QuadHeight = new GUIContent("Height", "Height of a Quad Layer");
SerializedProperty Property_QuadHeight;
static string PropertyName_EquirectRadius = "m_EquirectRadius";
static GUIContent Label_EquirectRadius = new GUIContent("Radius", "Radius of Equirect Layer");
SerializedProperty Property_EquirectRadius;
static string PropertyName_EquirectScaleX = "m_EquirectScaleX";
static GUIContent Label_EquirectScaleX = new GUIContent("scale.x", "Scale.X of Equirect Layer");
SerializedProperty Property_EquirectScaleX;
static string PropertyName_EquirectScaleY = "m_EquirectScaleY";
static GUIContent Label_EquirectScaleY = new GUIContent("scale.y", "Scale.Y of Equirect Layer");
SerializedProperty Property_EquirectScaleY;
static string PropertyName_EquirectBiasX = "m_EquirectBiasX";
static GUIContent Label_EquirectBiasX = new GUIContent("bias.x", "Bias.X of Equirect Layer");
SerializedProperty Property_EquirectBiasX;
static string PropertyName_EquirectBiasY = "m_EquirectBiasY";
static GUIContent Label_EquirectBiasY = new GUIContent("bias.y", "Bias.Y of Equirect Layer");
SerializedProperty Property_EquirectBiasY;
static string PropertyName_EquirectCentralHorizontalAngle = "m_EquirectCentralHorizontalAngle";
static GUIContent Label_EquirectCentralHorizontalAngle = new GUIContent("CentralHorizontalAngle", "Central Horizontal Angle of Equirect Layer");
SerializedProperty Property_EquirectCentralHorizontalAngle;
static string PropertyName_EquirectUpperVerticalAngle = "m_EquirectUpperVerticalAngle";
static GUIContent Label_EquirectUpperVerticalAngle = new GUIContent("UpperVerticalAngle", "Upper Vertical Angle of Equirect Layer");
SerializedProperty Property_EquirectUpperVerticalAngle;
static string PropertyName_EquirectLowerVerticalAngle = "m_EquirectLowerVerticalAngle";
static GUIContent Label_EquirectLowerVerticalAngle = new GUIContent("LowerVerticalAngle", "Lower Vertical Angle of Equirect Layer");
SerializedProperty Property_EquirectLowerVerticalAngle;
static string PropertyName_CylinderHeight = "m_CylinderHeight";
static GUIContent Label_CylinderHeight = new GUIContent("Height", "Height of Cylinder Layer");
SerializedProperty Property_CylinderHeight;
static string PropertyName_CylinderArcLength = "m_CylinderArcLength";
static GUIContent Label_CylinderArcLength = new GUIContent("Arc Length", "Arc Length of Cylinder Layer");
SerializedProperty Property_CylinderArcLength;
static string PropertyName_CylinderRadius = "m_CylinderRadius";
static GUIContent Label_CylinderRadius = new GUIContent("Radius", "Radius of Cylinder Layer");
SerializedProperty Property_CylinderRadius;
static string PropertyName_AngleOfArc = "m_CylinderAngleOfArc";
static GUIContent Label_AngleOfArc = new GUIContent("Arc Angle", "Central angle of arc of Cylinder Layer");
SerializedProperty Property_AngleOfArc;
static string PropertyName_isExternalSurface = "isExternalSurface";
static GUIContent Label_isExternalSurface = new GUIContent("External Surface", "Specify using external surface or not.");
SerializedProperty Property_isExternalSurface;
static string PropertyName_ExternalSurfaceWidth = "externalSurfaceWidth";
static GUIContent Label_ExternalSurfaceWidth = new GUIContent("Width");
SerializedProperty Property_ExternalSurfaceWidth;
static string PropertyName_ExternalSurfaceHeight = "externalSurfaceHeight";
static GUIContent Label_ExternalSurfaceHeight = new GUIContent("Height");
SerializedProperty Property_ExternalSurfaceHeight;
static string PropertyName_IsDynamicLayer = "isDynamicLayer";
static GUIContent Label_IsDynamicLayer = new GUIContent("Dynamic Layer", "Specify whether Layer needs to be updated each frame or not.");
SerializedProperty Property_IsDynamicLayer;
static string PropertyName_IsCustomRects = "isCustomRects";
static GUIContent Label_IsCustomRects = new GUIContent("Customize Rects", "Using a single texture as a stereo image");
SerializedProperty Property_IsCustomRects;
static string PropertyName_CustomRects = "customRects";
static GUIContent Label_CustomRects = new GUIContent("Customize Rects Type", "Specify the customize rects type of the left texture.");
SerializedProperty Property_CustomRects;
static string PropertyName_ApplyColorScaleBias = "applyColorScaleBias";
static GUIContent Label_ApplyColorScaleBias = new GUIContent("Apply Color Scale Bias", "Color scale and bias are applied to a layer color during composition, after its conversion to premultiplied alpha representation. LayerColor = LayerColor * colorScale + colorBias");
SerializedProperty Property_ApplyColorScaleBias;
static string PropertyName_SolidEffect = "solidEffect";
static GUIContent Label_SolidEffect = new GUIContent("Solid Effect", "Apply UnderLay Color Scale Bias in Runtime Level.");
SerializedProperty Property_SolidEffect;
static string PropertyName_ColorScale = "colorScale";
static GUIContent Label_ColorScale = new GUIContent("Color Scale", "Will be used for modulatting the color sourced from the images.");
SerializedProperty Property_ColorScale;
static string PropertyName_ColorBias = "colorBias";
static GUIContent Label_ColorBias = new GUIContent("Color Bias", "Will be used for offseting the color sourced from the images.");
SerializedProperty Property_ColorBias;
static string PropertyName_IsProtectedSurface = "isProtectedSurface";
static GUIContent Label_IsProtectedSurface = new GUIContent("Protected Surface");
SerializedProperty Property_IsProtectedSurface;
static string PropertyName_RenderPriority = "renderPriority";
static GUIContent Label_RenderPriority = new GUIContent("Render Priority", "When Auto Fallback is enabled, layers with a higher render priority will be rendered as normal layers first.");
SerializedProperty Property_RenderPriority;
static string PropertyName_TrackingOrigin = "trackingOrigin";
static GUIContent Label_TrackingOrigin = new GUIContent("Tracking Origin", "Assign the tracking origin here to offset the pose of the Composition Layer.");
SerializedProperty Property_TrackingOrigin;
private bool showLayerParams = true, showColorScaleBiasParams = true;
//private bool showExternalSurfaceParams = false;
Rect FullRect = new Rect(0, 0, 1, 1);
Rect LeftRightRect = new Rect(0, 0, 0.5f, 1);
Rect TopDownRect = new Rect(0, 0.5f, 1, 0.5f);
public override void OnInspectorGUI()
{
if (Property_LayerType == null) Property_LayerType = serializedObject.FindProperty(PropertyName_LayerType);
if (Property_CompositionDepth == null) Property_CompositionDepth = serializedObject.FindProperty(PropertyName_CompositionDepth);
if (Property_LayerShape == null) Property_LayerShape = serializedObject.FindProperty(PropertyName_LayerShape);
if (Property_LayerVisibility == null) Property_LayerVisibility = serializedObject.FindProperty(PropertyName_LayerVisibility);
if (Property_CustomRects == null) Property_CustomRects = serializedObject.FindProperty(PropertyName_CustomRects);
if (Property_LockMode == null) Property_LockMode = serializedObject.FindProperty(PropertyName_LockMode);
if (Property_QuadWidth == null) Property_QuadWidth = serializedObject.FindProperty(PropertyName_QuadWidth);
if (Property_QuadHeight == null) Property_QuadHeight = serializedObject.FindProperty(PropertyName_QuadHeight);
if (Property_EquirectRadius == null) Property_EquirectRadius = serializedObject.FindProperty(PropertyName_EquirectRadius);
if (Property_EquirectScaleX == null) Property_EquirectScaleX = serializedObject.FindProperty(PropertyName_EquirectScaleX);
if (Property_EquirectScaleY == null) Property_EquirectScaleY = serializedObject.FindProperty(PropertyName_EquirectScaleY);
if (Property_EquirectBiasX == null) Property_EquirectBiasX = serializedObject.FindProperty(PropertyName_EquirectBiasX);
if (Property_EquirectBiasY == null) Property_EquirectBiasY = serializedObject.FindProperty(PropertyName_EquirectBiasY);
if (Property_EquirectCentralHorizontalAngle == null) Property_EquirectCentralHorizontalAngle = serializedObject.FindProperty(PropertyName_EquirectCentralHorizontalAngle);
if (Property_EquirectUpperVerticalAngle == null) Property_EquirectUpperVerticalAngle = serializedObject.FindProperty(PropertyName_EquirectUpperVerticalAngle);
if (Property_EquirectLowerVerticalAngle == null) Property_EquirectLowerVerticalAngle = serializedObject.FindProperty(PropertyName_EquirectLowerVerticalAngle);
if (Property_CylinderHeight == null) Property_CylinderHeight = serializedObject.FindProperty(PropertyName_CylinderHeight);
if (Property_CylinderArcLength == null) Property_CylinderArcLength = serializedObject.FindProperty(PropertyName_CylinderArcLength);
if (Property_CylinderRadius == null) Property_CylinderRadius = serializedObject.FindProperty(PropertyName_CylinderRadius);
if (Property_AngleOfArc == null) Property_AngleOfArc = serializedObject.FindProperty(PropertyName_AngleOfArc);
if (Property_isExternalSurface == null) Property_isExternalSurface = serializedObject.FindProperty(PropertyName_isExternalSurface);
if (Property_ExternalSurfaceWidth == null) Property_ExternalSurfaceWidth = serializedObject.FindProperty(PropertyName_ExternalSurfaceWidth);
if (Property_ExternalSurfaceHeight == null) Property_ExternalSurfaceHeight = serializedObject.FindProperty(PropertyName_ExternalSurfaceHeight);
if (Property_IsDynamicLayer == null) Property_IsDynamicLayer = serializedObject.FindProperty(PropertyName_IsDynamicLayer);
if (Property_IsCustomRects == null) Property_IsCustomRects = serializedObject.FindProperty(PropertyName_IsCustomRects);
if (Property_ApplyColorScaleBias == null) Property_ApplyColorScaleBias = serializedObject.FindProperty(PropertyName_ApplyColorScaleBias);
if (Property_SolidEffect == null) Property_SolidEffect = serializedObject.FindProperty(PropertyName_SolidEffect);
if (Property_ColorScale == null) Property_ColorScale = serializedObject.FindProperty(PropertyName_ColorScale);
if (Property_ColorBias == null) Property_ColorBias = serializedObject.FindProperty(PropertyName_ColorBias);
if (Property_IsProtectedSurface == null) Property_IsProtectedSurface = serializedObject.FindProperty(PropertyName_IsProtectedSurface);
if (Property_RenderPriority == null) Property_RenderPriority = serializedObject.FindProperty(PropertyName_RenderPriority);
if (Property_TrackingOrigin == null) Property_TrackingOrigin = serializedObject.FindProperty(PropertyName_TrackingOrigin);
CompositionLayer targetCompositionLayer = target as CompositionLayer;
if (!FeatureHelpers.GetFeatureWithIdForBuildTarget(BuildTargetGroup.Android, ViveCompositionLayer.featureId).enabled)
{
EditorGUILayout.HelpBox("The Composition Layer feature is not enabled in OpenXR Settings.\nEnable it to use the Composition Layer.", MessageType.Warning);
}
EditorGUILayout.PropertyField(Property_LayerType, new GUIContent(Label_LayerType));
serializedObject.ApplyModifiedProperties();
EditorGUILayout.PropertyField(Property_CompositionDepth, new GUIContent(Label_CompositionDepth));
serializedObject.ApplyModifiedProperties();
EditorGUILayout.PropertyField(Property_LayerShape, new GUIContent(Label_LayerShape));
serializedObject.ApplyModifiedProperties();
if (Property_LayerShape.intValue == (int)CompositionLayer.LayerShape.Equirect || Property_LayerShape.intValue == (int)CompositionLayer.LayerShape.Equirect2)
{
if (targetCompositionLayer.isPreviewingQuad)
{
targetCompositionLayer.isPreviewingQuad = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
if (targetCompositionLayer.isPreviewingCylinder)
{
targetCompositionLayer.isPreviewingCylinder = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
if (!FeatureHelpers.GetFeatureWithIdForBuildTarget(BuildTargetGroup.Android, ViveCompositionLayerEquirect.featureId).enabled)
{
EditorGUILayout.HelpBox("The Composition Layer Equirect feature is not enabled in OpenXR Settings.\nEnable it to use Equirect layers.", MessageType.Warning);
}
EditorGUI.indentLevel++;
showLayerParams = EditorGUILayout.Foldout(showLayerParams, "Equirect Parameters");
if (showLayerParams)
{
EditorGUILayout.PropertyField(Property_EquirectRadius, new GUIContent(Label_EquirectRadius));
if (Property_LayerShape.intValue == (int)CompositionLayer.LayerShape.Equirect)
{
EditorGUILayout.PropertyField(Property_EquirectScaleX, new GUIContent(Label_EquirectScaleX));
EditorGUILayout.PropertyField(Property_EquirectScaleY, new GUIContent(Label_EquirectScaleY));
EditorGUILayout.PropertyField(Property_EquirectBiasX, new GUIContent(Label_EquirectBiasX));
EditorGUILayout.PropertyField(Property_EquirectBiasY, new GUIContent(Label_EquirectBiasY));
}
else if (Property_LayerShape.intValue == (int)CompositionLayer.LayerShape.Equirect2)
{
EditorGUILayout.PropertyField(Property_EquirectCentralHorizontalAngle, new GUIContent(Label_EquirectCentralHorizontalAngle));
EditorGUILayout.PropertyField(Property_EquirectUpperVerticalAngle, new GUIContent(Label_EquirectUpperVerticalAngle));
EditorGUILayout.PropertyField(Property_EquirectLowerVerticalAngle, new GUIContent(Label_EquirectLowerVerticalAngle));
}
serializedObject.ApplyModifiedProperties();
}
EditorGUI.indentLevel--;
bool EquirectParamsChanged = targetCompositionLayer.LayerDimensionsChanged();
if (targetCompositionLayer.isPreviewingEquirect)
{
Transform generatedPreviewTransform = targetCompositionLayer.transform.Find(CompositionLayer.EquirectPreviewName);
if (generatedPreviewTransform != null)
{
targetCompositionLayer.generatedPreview = generatedPreviewTransform.gameObject;
if (EquirectParamsChanged)
{
MeshFilter equirectMeshFilter = targetCompositionLayer.generatedPreview.GetComponent<MeshFilter>();
//Generate vertices
equirectMeshFilter.mesh = CompositionLayer.MeshGenerationHelper.GenerateEquirectMesh(targetCompositionLayer.hmd, targetCompositionLayer.EquirectRadius);
targetCompositionLayer.generatedPreview.transform.localPosition = Vector3.zero;
targetCompositionLayer.generatedPreview.transform.localRotation = Quaternion.identity;
targetCompositionLayer.generatedPreview.transform.localScale = targetCompositionLayer.GetNormalizedLocalScale(targetCompositionLayer.transform, Vector3.one);
}
if (targetCompositionLayer.generatedPreview.GetComponent<MeshRenderer>().sharedMaterial.mainTexture != targetCompositionLayer.texture)
{
targetCompositionLayer.generatedPreview.GetComponent<MeshRenderer>().sharedMaterial.mainTexture = targetCompositionLayer.texture;
}
if (GUILayout.Button("Hide Equirect Preview"))
{
targetCompositionLayer.isPreviewingEquirect = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
}
else
{
targetCompositionLayer.isPreviewingEquirect = false;
}
}
else
{
if (GUILayout.Button("Show Equirect Preview"))
{
Rect srcRectLeft = FullRect;
if (targetCompositionLayer.isCustomRects && targetCompositionLayer.customRects == CompositionLayer.CustomRectsType.LeftRight)
srcRectLeft = LeftRightRect;
if (targetCompositionLayer.isCustomRects && targetCompositionLayer.customRects == CompositionLayer.CustomRectsType.TopDown)
srcRectLeft = TopDownRect;
targetCompositionLayer.isPreviewingEquirect = true;
//Vector3[] cylinderVertices = CompositionLayer.MeshGenerationHelper.GenerateCylinderVertex(targetCompositionLayer.CylinderAngleOfArc, targetCompositionLayer.CylinderRadius, targetCompositionLayer.CylinderHeight);
//Add components to Game Object
targetCompositionLayer.generatedPreview = new GameObject();
targetCompositionLayer.generatedPreview.hideFlags = HideFlags.HideAndDontSave;
targetCompositionLayer.generatedPreview.name = CompositionLayer.EquirectPreviewName;
targetCompositionLayer.generatedPreview.transform.SetParent(targetCompositionLayer.gameObject.transform);
targetCompositionLayer.generatedPreview.transform.localPosition = Vector3.zero;
targetCompositionLayer.generatedPreview.transform.localRotation = Quaternion.identity;
targetCompositionLayer.generatedPreview.transform.localScale = targetCompositionLayer.GetNormalizedLocalScale(targetCompositionLayer.transform, Vector3.one);
MeshRenderer equirectMeshRenderer = targetCompositionLayer.generatedPreview.AddComponent<MeshRenderer>();
MeshFilter equirectMeshFilter = targetCompositionLayer.generatedPreview.AddComponent<MeshFilter>();
equirectMeshRenderer.sharedMaterial = new Material(Shader.Find("Unlit/Transparent"));
if (targetCompositionLayer.texture != null)
{
equirectMeshRenderer.sharedMaterial.mainTexture = targetCompositionLayer.texture;
equirectMeshRenderer.sharedMaterial.mainTextureOffset = srcRectLeft.position;
equirectMeshRenderer.sharedMaterial.mainTextureScale = srcRectLeft.size;
}
//Generate Mesh
equirectMeshFilter.mesh = CompositionLayer.MeshGenerationHelper.GenerateEquirectMesh(targetCompositionLayer.hmd, targetCompositionLayer.EquirectRadius);
}
}
EditorGUILayout.Space(10);
serializedObject.ApplyModifiedProperties();
}
if (Property_LayerShape.intValue == (int)CompositionLayer.LayerShape.Cylinder)
{
if (!FeatureHelpers.GetFeatureWithIdForBuildTarget(BuildTargetGroup.Android, ViveCompositionLayerCylinder.featureId).enabled)
{
EditorGUILayout.HelpBox("The Composition Layer Cylinder feature is not enabled in OpenXR Settings.\nEnable it to use Cylinder layers.", MessageType.Warning);
}
if (targetCompositionLayer.isPreviewingQuad)
{
targetCompositionLayer.isPreviewingQuad = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
if (targetCompositionLayer.isPreviewingEquirect)
{
targetCompositionLayer.isPreviewingEquirect = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
Transform generatedQuadTransform = targetCompositionLayer.transform.Find(CompositionLayer.QuadUnderlayMeshName);
if (generatedQuadTransform != null)
{
DestroyImmediate(generatedQuadTransform.gameObject);
}
EditorGUI.indentLevel++;
showLayerParams = EditorGUILayout.Foldout(showLayerParams, "Cylinder Parameters");
if (showLayerParams)
{
float radiusLowerBound = Mathf.Max(0.001f, CompositionLayer.CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(targetCompositionLayer.cylinderArcLength, targetCompositionLayer.angleOfArcUpperLimit));
float radiusUpperBound = CompositionLayer.CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(targetCompositionLayer.cylinderArcLength, targetCompositionLayer.angleOfArcLowerLimit);
EditorGUILayout.HelpBox("Changing the Arc Length will affect the upper and lower bounds of the radius.\nUpper Bound of Radius: " + radiusUpperBound + "\nLower Bound of Radius: " + radiusLowerBound, MessageType.Info);
EditorGUILayout.PropertyField(Property_CylinderRadius, new GUIContent(Label_CylinderRadius));
EditorGUILayout.PropertyField(Property_LockMode, new GUIContent(Label_LockMode));
serializedObject.ApplyModifiedProperties();
EditorGUILayout.HelpBox("Arc Length and Arc Angle are correlated, adjusting one of them changes the other as well. The Radius will not be changed when adjusting these two values.", MessageType.Info);
if (targetCompositionLayer.lockMode == CompositionLayer.CylinderLayerParamLockMode.ArcLength)
{
GUI.enabled = false;
EditorGUILayout.PropertyField(Property_CylinderArcLength, new GUIContent(Label_CylinderArcLength));
GUI.enabled = true;
EditorGUILayout.Slider(Property_AngleOfArc, targetCompositionLayer.angleOfArcLowerLimit, targetCompositionLayer.angleOfArcUpperLimit, new GUIContent(Label_AngleOfArc));
}
else if (targetCompositionLayer.lockMode == CompositionLayer.CylinderLayerParamLockMode.ArcAngle)
{
EditorGUILayout.PropertyField(Property_CylinderArcLength, new GUIContent(Label_CylinderArcLength));
GUI.enabled = false;
EditorGUILayout.Slider(Property_AngleOfArc, targetCompositionLayer.angleOfArcLowerLimit, targetCompositionLayer.angleOfArcUpperLimit, new GUIContent(Label_AngleOfArc));
GUI.enabled = true;
}
EditorGUILayout.PropertyField(Property_CylinderHeight, new GUIContent(Label_CylinderHeight));
}
EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
CompositionLayer.CylinderLayerParamAdjustmentMode currentAdjustmentMode = targetCompositionLayer.CurrentAdjustmentMode();
switch (currentAdjustmentMode)
{
case CompositionLayer.CylinderLayerParamAdjustmentMode.ArcLength:
{
targetCompositionLayer.CylinderAngleOfArc = CompositionLayer.CylinderParameterHelper.RadiusAndArcLengthToDegAngleOfArc(targetCompositionLayer.CylinderArcLength, targetCompositionLayer.CylinderRadius);
float cylinderArcLengthRef = targetCompositionLayer.CylinderArcLength;
if (!ArcLengthValidityCheck(ref cylinderArcLengthRef, targetCompositionLayer.CylinderRadius, targetCompositionLayer.angleOfArcLowerLimit, targetCompositionLayer.angleOfArcUpperLimit))
{
targetCompositionLayer.CylinderArcLength = cylinderArcLengthRef;
targetCompositionLayer.CylinderAngleOfArc = CompositionLayer.CylinderParameterHelper.RadiusAndArcLengthToDegAngleOfArc(targetCompositionLayer.CylinderArcLength, targetCompositionLayer.CylinderRadius);
}
serializedObject.ApplyModifiedProperties();
break;
}
case CompositionLayer.CylinderLayerParamAdjustmentMode.ArcAngle:
{
targetCompositionLayer.CylinderArcLength = CompositionLayer.CylinderParameterHelper.RadiusAndDegAngleOfArcToArcLength(targetCompositionLayer.CylinderAngleOfArc, targetCompositionLayer.CylinderRadius);
serializedObject.ApplyModifiedProperties();
break;
}
case CompositionLayer.CylinderLayerParamAdjustmentMode.Radius:
default:
{
float cylinderRadiusRef = targetCompositionLayer.CylinderRadius;
RadiusValidityCheck(targetCompositionLayer.CylinderArcLength, ref cylinderRadiusRef, targetCompositionLayer.angleOfArcLowerLimit, targetCompositionLayer.angleOfArcUpperLimit, targetCompositionLayer.lockMode);
targetCompositionLayer.CylinderRadius = cylinderRadiusRef;
if (targetCompositionLayer.lockMode == CompositionLayer.CylinderLayerParamLockMode.ArcLength)
{
targetCompositionLayer.CylinderAngleOfArc = CompositionLayer.CylinderParameterHelper.RadiusAndArcLengthToDegAngleOfArc(targetCompositionLayer.CylinderArcLength, targetCompositionLayer.CylinderRadius);
float cylinderArcLengthRef = targetCompositionLayer.CylinderArcLength;
if (!ArcLengthValidityCheck(ref cylinderArcLengthRef, targetCompositionLayer.CylinderRadius, targetCompositionLayer.angleOfArcLowerLimit, targetCompositionLayer.angleOfArcUpperLimit))
{
targetCompositionLayer.CylinderArcLength = cylinderArcLengthRef;
targetCompositionLayer.CylinderAngleOfArc = CompositionLayer.CylinderParameterHelper.RadiusAndArcLengthToDegAngleOfArc(targetCompositionLayer.CylinderArcLength, targetCompositionLayer.CylinderRadius);
}
}
else if (targetCompositionLayer.lockMode == CompositionLayer.CylinderLayerParamLockMode.ArcAngle)
{
targetCompositionLayer.CylinderArcLength = CompositionLayer.CylinderParameterHelper.RadiusAndDegAngleOfArcToArcLength(targetCompositionLayer.CylinderAngleOfArc, targetCompositionLayer.CylinderRadius);
}
serializedObject.ApplyModifiedProperties();
break;
}
}
EditorGUILayout.HelpBox("Current Layer Aspect Ratio (Arc Length : Height) = " + targetCompositionLayer.CylinderArcLength + " : " + targetCompositionLayer.CylinderHeight, MessageType.Info);
Vector3 CompositionLayerScale = targetCompositionLayer.gameObject.transform.localScale;
bool CylinderParamsChanged = targetCompositionLayer.LayerDimensionsChanged();
if (targetCompositionLayer.isPreviewingCylinder)
{
Transform generatedPreviewTransform = targetCompositionLayer.transform.Find(CompositionLayer.CylinderPreviewName);
if (generatedPreviewTransform != null)
{
targetCompositionLayer.generatedPreview = generatedPreviewTransform.gameObject;
if (CylinderParamsChanged)
{
//Generate vertices
Vector3[] cylinderVertices = CompositionLayer.MeshGenerationHelper.GenerateCylinderVertex(targetCompositionLayer.CylinderAngleOfArc, targetCompositionLayer.CylinderRadius, targetCompositionLayer.CylinderHeight);;
MeshFilter cylinderMeshFilter = targetCompositionLayer.generatedPreview.GetComponent<MeshFilter>();
//Generate Mesh
cylinderMeshFilter.mesh = CompositionLayer.MeshGenerationHelper.GenerateCylinderMesh(targetCompositionLayer.CylinderAngleOfArc, cylinderVertices);
targetCompositionLayer.generatedPreview.transform.localPosition = Vector3.zero;
targetCompositionLayer.generatedPreview.transform.localRotation = Quaternion.identity;
targetCompositionLayer.generatedPreview.transform.localScale = targetCompositionLayer.GetNormalizedLocalScale(targetCompositionLayer.transform, Vector3.one);
}
if (targetCompositionLayer.generatedPreview.GetComponent<MeshRenderer>().sharedMaterial.mainTexture != targetCompositionLayer.texture)
{
targetCompositionLayer.generatedPreview.GetComponent<MeshRenderer>().sharedMaterial.mainTexture = targetCompositionLayer.texture;
}
if (GUILayout.Button("Hide Cylinder Preview"))
{
targetCompositionLayer.isPreviewingCylinder = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
}
else
{
targetCompositionLayer.isPreviewingCylinder = false;
}
}
else
{
if (GUILayout.Button("Show Cylinder Preview"))
{
Rect srcRectLeft = FullRect;
if (targetCompositionLayer.isCustomRects && targetCompositionLayer.customRects == CompositionLayer.CustomRectsType.LeftRight)
srcRectLeft = LeftRightRect;
if (targetCompositionLayer.isCustomRects && targetCompositionLayer.customRects == CompositionLayer.CustomRectsType.TopDown)
srcRectLeft = TopDownRect;
targetCompositionLayer.isPreviewingCylinder = true;
Vector3[] cylinderVertices = CompositionLayer.MeshGenerationHelper.GenerateCylinderVertex(targetCompositionLayer.CylinderAngleOfArc, targetCompositionLayer.CylinderRadius, targetCompositionLayer.CylinderHeight);
//Add components to Game Object
targetCompositionLayer.generatedPreview = new GameObject();
targetCompositionLayer.generatedPreview.hideFlags = HideFlags.HideAndDontSave;
targetCompositionLayer.generatedPreview.name = CompositionLayer.CylinderPreviewName;
targetCompositionLayer.generatedPreview.transform.SetParent(targetCompositionLayer.gameObject.transform);
targetCompositionLayer.generatedPreview.transform.localPosition = Vector3.zero;
targetCompositionLayer.generatedPreview.transform.localRotation = Quaternion.identity;
targetCompositionLayer.generatedPreview.transform.localScale = targetCompositionLayer.GetNormalizedLocalScale(targetCompositionLayer.transform, Vector3.one);
MeshRenderer cylinderMeshRenderer = targetCompositionLayer.generatedPreview.AddComponent<MeshRenderer>();
MeshFilter cylinderMeshFilter = targetCompositionLayer.generatedPreview.AddComponent<MeshFilter>();
cylinderMeshRenderer.sharedMaterial = new Material(Shader.Find("Unlit/Transparent"));
if (targetCompositionLayer.texture != null)
{
cylinderMeshRenderer.sharedMaterial.mainTexture = targetCompositionLayer.texture;
cylinderMeshRenderer.sharedMaterial.mainTextureOffset = srcRectLeft.position;
cylinderMeshRenderer.sharedMaterial.mainTextureScale = srcRectLeft.size;
}
//Generate Mesh
cylinderMeshFilter.mesh = CompositionLayer.MeshGenerationHelper.GenerateCylinderMesh(targetCompositionLayer.CylinderAngleOfArc, cylinderVertices);
}
}
EditorGUILayout.Space(10);
serializedObject.ApplyModifiedProperties();
}
else if (Property_LayerShape.intValue == (int)CompositionLayer.LayerShape.Quad)
{
if (targetCompositionLayer.isPreviewingCylinder)
{
targetCompositionLayer.isPreviewingCylinder = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
if (targetCompositionLayer.isPreviewingEquirect)
{
targetCompositionLayer.isPreviewingEquirect = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
EditorGUI.indentLevel++;
showLayerParams = EditorGUILayout.Foldout(showLayerParams, "Quad Parameters");
if (showLayerParams)
{
EditorGUILayout.PropertyField(Property_QuadWidth, new GUIContent(Label_QuadWidth));
EditorGUILayout.PropertyField(Property_QuadHeight, new GUIContent(Label_QuadHeight));
}
EditorGUI.indentLevel--;
EditorGUILayout.HelpBox("Current Layer Aspect Ratio (Width : Height) = " + targetCompositionLayer.quadWidth + " : " + targetCompositionLayer.quadHeight, MessageType.Info);
Vector3 CompositionLayerScale = targetCompositionLayer.gameObject.transform.localScale;
bool QuadParamsChanged = targetCompositionLayer.LayerDimensionsChanged();
if (targetCompositionLayer.isPreviewingQuad)
{
Transform generatedPreviewTransform = targetCompositionLayer.transform.Find(CompositionLayer.QuadPreviewName);
if (generatedPreviewTransform != null)
{
targetCompositionLayer.generatedPreview = generatedPreviewTransform.gameObject;
if (QuadParamsChanged)
{
//Generate vertices
Vector3[] quadVertices = CompositionLayer.MeshGenerationHelper.GenerateQuadVertex(targetCompositionLayer.quadWidth, targetCompositionLayer.quadHeight);
MeshFilter quadMeshFilter = targetCompositionLayer.generatedPreview.GetComponent<MeshFilter>();
//Generate Mesh
quadMeshFilter.mesh = CompositionLayer.MeshGenerationHelper.GenerateQuadMesh(quadVertices);
targetCompositionLayer.generatedPreview.transform.localPosition = Vector3.zero;
targetCompositionLayer.generatedPreview.transform.localRotation = Quaternion.identity;
targetCompositionLayer.generatedPreview.transform.localScale = targetCompositionLayer.GetNormalizedLocalScale(targetCompositionLayer.transform, Vector3.one);
}
if (targetCompositionLayer.generatedPreview.GetComponent<MeshRenderer>().sharedMaterial.mainTexture != targetCompositionLayer.texture)
{
targetCompositionLayer.generatedPreview.GetComponent<MeshRenderer>().sharedMaterial.mainTexture = targetCompositionLayer.texture;
}
if (GUILayout.Button("Hide Quad Preview"))
{
targetCompositionLayer.isPreviewingQuad = false;
if (targetCompositionLayer.generatedPreview != null)
{
DestroyImmediate(targetCompositionLayer.generatedPreview);
}
}
}
else
{
targetCompositionLayer.isPreviewingQuad = false;
}
}
else
{
if (GUILayout.Button("Show Quad Preview"))
{
Rect srcRectLeft = FullRect;
if (targetCompositionLayer.isCustomRects && targetCompositionLayer.customRects == CompositionLayer.CustomRectsType.LeftRight)
srcRectLeft = LeftRightRect;
if (targetCompositionLayer.isCustomRects && targetCompositionLayer.customRects == CompositionLayer.CustomRectsType.TopDown)
srcRectLeft = TopDownRect;
targetCompositionLayer.isPreviewingQuad = true;
//Generate vertices
Vector3[] quadVertices = CompositionLayer.MeshGenerationHelper.GenerateQuadVertex(targetCompositionLayer.quadWidth, targetCompositionLayer.quadHeight);
//Add components to Game Object
targetCompositionLayer.generatedPreview = new GameObject();
targetCompositionLayer.generatedPreview.hideFlags = HideFlags.HideAndDontSave;
targetCompositionLayer.generatedPreview.name = CompositionLayer.QuadPreviewName;
targetCompositionLayer.generatedPreview.transform.SetParent(targetCompositionLayer.gameObject.transform);
targetCompositionLayer.generatedPreview.transform.localPosition = Vector3.zero;
targetCompositionLayer.generatedPreview.transform.localRotation = Quaternion.identity;
targetCompositionLayer.generatedPreview.transform.localScale = targetCompositionLayer.GetNormalizedLocalScale(targetCompositionLayer.transform, Vector3.one);
MeshRenderer quadMeshRenderer = targetCompositionLayer.generatedPreview.AddComponent<MeshRenderer>();
MeshFilter quadMeshFilter = targetCompositionLayer.generatedPreview.AddComponent<MeshFilter>();
quadMeshRenderer.sharedMaterial = new Material(Shader.Find("Unlit/Transparent"));
if (targetCompositionLayer.texture != null)
{
quadMeshRenderer.sharedMaterial.mainTexture = targetCompositionLayer.texture;
quadMeshRenderer.sharedMaterial.mainTextureOffset = srcRectLeft.position;
quadMeshRenderer.sharedMaterial.mainTextureScale = srcRectLeft.size;
}
//Generate Mesh
quadMeshFilter.mesh = CompositionLayer.MeshGenerationHelper.GenerateQuadMesh(quadVertices);
}
}
}
//Rect UI For textures
Rect labelRect = EditorGUILayout.GetControlRect();
EditorGUI.LabelField(new Rect(labelRect.x, labelRect.y, labelRect.width / 2, labelRect.height), new GUIContent("Left Texture", "Texture used for the left eye"));
EditorGUI.LabelField(new Rect(labelRect.x + labelRect.width / 2, labelRect.y, labelRect.width / 2, labelRect.height), new GUIContent("Right Texture", "Texture used for the right eye"));
Rect textureRect = EditorGUILayout.GetControlRect(GUILayout.Height(64));
targetCompositionLayer.texture = (Texture)EditorGUI.ObjectField(new Rect(textureRect.x, textureRect.y, 64, textureRect.height), targetCompositionLayer.texture, typeof(Texture), true);
targetCompositionLayer.textureRight = (Texture)EditorGUI.ObjectField(new Rect(textureRect.x + textureRect.width / 2, textureRect.y, 64, textureRect.height), targetCompositionLayer.textureRight, typeof(Texture), true);
if (null == targetCompositionLayer.textureLeft)
{
targetCompositionLayer.texture = targetCompositionLayer.textureRight;
//myScript.textures[1] = right;
}
EditorGUILayout.PropertyField(Property_LayerVisibility, new GUIContent(Label_LayerVisibility));
serializedObject.ApplyModifiedProperties();
EditorGUILayout.PropertyField(Property_IsDynamicLayer, Label_IsDynamicLayer);
serializedObject.ApplyModifiedProperties();
//EditorGUILayout.PropertyField(Property_isExternalSurface, Label_isExternalSurface);
//serializedObject.ApplyModifiedProperties();
//if (targetCompositionLayer.isExternalSurface)
/*if (false)
{
EditorGUI.indentLevel++;
showExternalSurfaceParams = EditorGUILayout.Foldout(showExternalSurfaceParams, "External Surface Parameters");
if (showExternalSurfaceParams)
{
EditorGUILayout.PropertyField(Property_ExternalSurfaceWidth, Label_ExternalSurfaceWidth);
EditorGUILayout.PropertyField(Property_ExternalSurfaceHeight, Label_ExternalSurfaceHeight);
EditorGUILayout.PropertyField(Property_IsProtectedSurface, Label_IsProtectedSurface);
serializedObject.ApplyModifiedProperties();
}
EditorGUI.indentLevel--;
}*/
if((Property_LayerShape.intValue != (int)CompositionLayer.LayerShape.Equirect && Property_LayerShape.intValue != (int)CompositionLayer.LayerShape.Equirect2) && (targetCompositionLayer.textureLeft == targetCompositionLayer.textureRight || targetCompositionLayer.textureRight == null))
{
EditorGUILayout.PropertyField(Property_IsCustomRects, Label_IsCustomRects);
serializedObject.ApplyModifiedProperties();
}
if (targetCompositionLayer.isCustomRects)
{
EditorGUILayout.PropertyField(Property_CustomRects, new GUIContent(Label_CustomRects));
serializedObject.ApplyModifiedProperties();
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(Property_ApplyColorScaleBias, Label_ApplyColorScaleBias);
serializedObject.ApplyModifiedProperties();
if (targetCompositionLayer.applyColorScaleBias)
{
if(!FeatureHelpers.GetFeatureWithIdForBuildTarget(BuildTargetGroup.Android, ViveCompositionLayerColorScaleBias.featureId).enabled)
{
EditorGUILayout.HelpBox("The Color Scale Bias feature is not enabled in OpenXR Settings.", MessageType.Warning);
}
EditorGUI.indentLevel++;
if (targetCompositionLayer.layerType == CompositionLayer.LayerType.Underlay)
{
EditorGUILayout.PropertyField(Property_SolidEffect, Label_SolidEffect);
serializedObject.ApplyModifiedProperties();
}
showColorScaleBiasParams = EditorGUILayout.Foldout(showColorScaleBiasParams, "Color Scale Bias Parameters");
if (showColorScaleBiasParams)
{
EditorGUILayout.PropertyField(Property_ColorScale, Label_ColorScale);
EditorGUILayout.PropertyField(Property_ColorBias, Label_ColorBias);
serializedObject.ApplyModifiedProperties();
}
EditorGUI.indentLevel--;
}
ViveCompositionLayer compositionLayerFeature = (ViveCompositionLayer)FeatureHelpers.GetFeatureWithIdForBuildTarget(BuildTargetGroup.Android, ViveCompositionLayer.featureId);
if (compositionLayerFeature != null && compositionLayerFeature.enableAutoFallback)
{
EditorGUILayout.PropertyField(Property_RenderPriority, new GUIContent(Label_RenderPriority));
serializedObject.ApplyModifiedProperties();
}
EditorGUILayout.PropertyField(Property_TrackingOrigin, Label_TrackingOrigin);
serializedObject.ApplyModifiedProperties();
}
public static bool RadiusValidityCheck(float inArcLength, ref float inRadius, float thetaLowerLimit, float thetaUpperLimit, CompositionLayer.CylinderLayerParamLockMode lockMode)
{
bool isValid = true;
if (inRadius <= 0)
{
inRadius = CompositionLayer.CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(inArcLength, thetaUpperLimit);
isValid = false;
return isValid;
}
float degThetaResult = CompositionLayer.CylinderParameterHelper.RadiusAndArcLengthToDegAngleOfArc(inArcLength, inRadius);
if (degThetaResult < thetaLowerLimit)
{
if (lockMode == CompositionLayer.CylinderLayerParamLockMode.ArcAngle) //Angle locked, increase arc length
{
ArcLengthValidityCheck(ref inArcLength, inRadius, thetaLowerLimit, thetaUpperLimit);
inRadius = CompositionLayer.CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(inArcLength, thetaLowerLimit);
}
else if (lockMode == CompositionLayer.CylinderLayerParamLockMode.ArcLength) //ArcLength Locked, keep angle at min
{
inRadius = CompositionLayer.CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(inArcLength, thetaLowerLimit);
}
isValid = false;
}
else if (degThetaResult > thetaUpperLimit)
{
if (lockMode == CompositionLayer.CylinderLayerParamLockMode.ArcAngle) //Angle locked, decrease arc length
{
ArcLengthValidityCheck(ref inArcLength, inRadius, thetaLowerLimit, thetaUpperLimit);
inRadius = CompositionLayer.CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(inArcLength, thetaUpperLimit);
}
else if (lockMode == CompositionLayer.CylinderLayerParamLockMode.ArcLength) //ArcLength Locked, keep angle at max
{
inRadius = CompositionLayer.CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(inArcLength, thetaUpperLimit);
}
isValid = false;
}
return isValid;
}
public static bool ArcLengthValidityCheck(ref float inArcLength, float inRadius, float thetaLowerLimit, float thetaUpperLimit)
{
bool isValid = true;
if (inArcLength <= 0)
{
inArcLength = CompositionLayer.CylinderParameterHelper.RadiusAndDegAngleOfArcToArcLength(thetaLowerLimit, inRadius);
isValid = false;
return isValid;
}
float degThetaResult = CompositionLayer.CylinderParameterHelper.RadiusAndArcLengthToDegAngleOfArc(inArcLength, inRadius);
if (degThetaResult < thetaLowerLimit)
{
inArcLength = CompositionLayer.CylinderParameterHelper.RadiusAndDegAngleOfArcToArcLength(thetaLowerLimit, inRadius);
isValid = false;
}
else if (degThetaResult > thetaUpperLimit)
{
inArcLength = CompositionLayer.CylinderParameterHelper.RadiusAndDegAngleOfArcToArcLength(thetaUpperLimit, inRadius);
isValid = false;
}
return isValid;
}
}
#endregion
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ec04348c6d3a8ce43ada545a76766344
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,308 @@
// "VIVE SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the VIVE SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace VIVE.OpenXR.CompositionLayer.Editor
{
using UnityEditor;
using UnityEngine.UI;
[CustomEditor(typeof(CompositionLayerUICanvas))]
public class CompositionLayerUICanvasEditor : Editor
{
static string PropertyName_MaxRenderTextureSize = "maxRenderTextureSize";
static GUIContent Label_MaxRenderTextureSize = new GUIContent("Max Render Texture Size", "Maximum render texture dimension. e.g. If maxRenderTextureSize is 1024, the render texture dimensions of a canvas with an Aspect Ratio of 2:1 will be 1024 x 512.");
SerializedProperty Property_MaxRenderTextureSize;
static string PropertyName_LayerType = "layerType";
static GUIContent Label_LayerType = new GUIContent("Layer Type", "Overlays render on top of all in-game objects.\nUnderlays can be occluded by in-game objects but may introduce alpha blending issues with transparent objects.");
SerializedProperty Property_LayerType;
static string PropertyName_LayerVisibility = "layerVisibility";
static GUIContent Label_LayerVisibility = new GUIContent("Visibility", "Specify the visibility of the layer.");
SerializedProperty Property_LayerVisibility;
static string PropertyName_CameraBGColor = "cameraBGColor";
static GUIContent Label_CameraBGColor = new GUIContent("Camera Background Color", "Background color of the camera for rendering the Canvas to the render texture target.\nChanging this option will affect the tint of the final image of the Canvas if no background gameobject is assigned.");
SerializedProperty Property_CameraBGColor;
static string PropertyName_BackgroundGO = "backgroundGO";
static GUIContent Label_BackgroundGO = new GUIContent("Background GameObject", "GameObject that contains a UI Component and will be used as the background of the Canvas.\nWhen succesfully assigned, the area which the background UI component covers will no longer be affected by the background color of the camera.");
SerializedProperty Property_BackgroundGO;
static string PropertyName_EnableAlphaBlendingCorrection = "enableAlphaBlendingCorrection";
static GUIContent Label_EnableAlphaBlendingCorrection = new GUIContent("Enable Alpha Blending Correction", "Enable this option if transparent UI elements are rendering darker than expected in an overall sense.\nNote that enabling this option will consume more resources.");
SerializedProperty Property_EnableAlphaBlendingCorrection;
static string PropertyName_CompositionDepth = "compositionDepth";
static GUIContent Label_CompositionDepth = new GUIContent("Composition Depth", "Specify Layer Composition Depth.");
SerializedProperty Property_CompositionDepth;
static string PropertyName_RenderPriority = "renderPriority";
static GUIContent Label_RenderPriority = new GUIContent("Render Priority", "When Auto Fallback is enabled, layers with a higher render priority will be rendered as normal layers first.");
SerializedProperty Property_RenderPriority;
static string PropertyName_TrackingOrigin = "trackingOrigin";
static GUIContent Label_TrackingOrigin = new GUIContent("Tracking Origin", "Assign the tracking origin here to offset the pose of the Composition Layer.");
SerializedProperty Property_TrackingOrigin;
bool isCurrentBackgroundGOValid = true, isMaterialReplaced = true, backgroundUINotFoundError = false, backgroundObjectNotChildError = false;
List<GameObject> validbackgroundGO;
private const string layerNameString = "CompositionLayerUICanvas";
public override void OnInspectorGUI()
{
//Check if current selected layer is rendered by main camera
if (Property_MaxRenderTextureSize == null) Property_MaxRenderTextureSize = serializedObject.FindProperty(PropertyName_MaxRenderTextureSize);
if (Property_LayerType == null) Property_LayerType = serializedObject.FindProperty(PropertyName_LayerType);
if (Property_LayerVisibility == null) Property_LayerVisibility = serializedObject.FindProperty(PropertyName_LayerVisibility);
if (Property_CameraBGColor == null) Property_CameraBGColor = serializedObject.FindProperty(PropertyName_CameraBGColor);
if (Property_BackgroundGO == null) Property_BackgroundGO = serializedObject.FindProperty(PropertyName_BackgroundGO);
if (Property_EnableAlphaBlendingCorrection == null) Property_EnableAlphaBlendingCorrection = serializedObject.FindProperty(PropertyName_EnableAlphaBlendingCorrection);
if (Property_CompositionDepth == null) Property_CompositionDepth = serializedObject.FindProperty(PropertyName_CompositionDepth);
if (Property_RenderPriority == null) Property_RenderPriority = serializedObject.FindProperty(PropertyName_RenderPriority);
if (Property_TrackingOrigin == null) Property_TrackingOrigin = serializedObject.FindProperty(PropertyName_TrackingOrigin);
CompositionLayerUICanvas targetLayerCanvasUI = target as CompositionLayerUICanvas;
Graphic[] graphicComponents = targetLayerCanvasUI.GetComponentsInChildren<Graphic>();
EditorGUILayout.HelpBox("CompositionLayerUICanvas will automatically generate the components necessary for rendering UI Canvas(es) with CompositionLayer(s).", MessageType.Info);
EditorGUILayout.PropertyField(Property_MaxRenderTextureSize, Label_MaxRenderTextureSize);
serializedObject.ApplyModifiedProperties();
EditorGUILayout.PropertyField(Property_LayerType, Label_LayerType);
serializedObject.ApplyModifiedProperties();
if (targetLayerCanvasUI.layerType == CompositionLayer.LayerType.Underlay)
{
EditorGUILayout.HelpBox("When using Underlay, overlapping non-opaque canvas elements (i.e. elements with alpha value < 1) might look different during runtime due to inherent alpha blending limitations.\n", MessageType.Warning);
EditorGUILayout.PropertyField(Property_EnableAlphaBlendingCorrection, Label_EnableAlphaBlendingCorrection);
serializedObject.ApplyModifiedProperties();
}
else
{
targetLayerCanvasUI.enableAlphaBlendingCorrection = false;
}
EditorGUILayout.PropertyField(Property_LayerVisibility, new GUIContent(Label_LayerVisibility));
serializedObject.ApplyModifiedProperties();
if (isCurrentBackgroundGOValid) //Cache valid result
{
validbackgroundGO = new List<GameObject>();
foreach (GameObject backgroundGO in targetLayerCanvasUI.backgroundGO)
{
validbackgroundGO.Add(backgroundGO);
}
}
List<GameObject> prevBackgroundGO = new List<GameObject>();
foreach (GameObject backgroundGO in targetLayerCanvasUI.backgroundGO)
{
prevBackgroundGO.Add(backgroundGO);
}
EditorGUILayout.PropertyField(Property_BackgroundGO, Label_BackgroundGO);
serializedObject.ApplyModifiedProperties();
bool needMaterialReplacement = false;
if (targetLayerCanvasUI.backgroundGO != null)
{
List<Graphic> backgroundGraphics = new List<Graphic>();
foreach (GameObject backgroundGO in targetLayerCanvasUI.backgroundGO)
{
if (backgroundGO == null) continue;
backgroundGraphics.Add(backgroundGO.GetComponent<Graphic>());
}
bool backgroundGraphicIsInChild = false;
if (backgroundGraphics.Count > 0)
{
foreach (Graphic backgroundGraphic in backgroundGraphics) //Loop through graphic components of selected background objects
{
if (backgroundGraphic != null)
{
backgroundUINotFoundError = false;
foreach (Graphic graphicComponent in graphicComponents) //Loop through graphic components under current canvas
{
if (graphicComponent == backgroundGraphic)
{
backgroundGraphicIsInChild = true;
backgroundObjectNotChildError = false;
break;
}
backgroundGraphicIsInChild = false;
}
if (!backgroundGraphicIsInChild) //Triggers when one of the selected objects is invalid
{
backgroundObjectNotChildError = true;
break;
}
}
else
{
backgroundUINotFoundError = true;
break;
}
}
if (!backgroundUINotFoundError && !backgroundObjectNotChildError)
{
isCurrentBackgroundGOValid = true;
foreach (GameObject backgroundGOCurr in targetLayerCanvasUI.backgroundGO)
{
if (backgroundGOCurr == null) continue;
if (!prevBackgroundGO.Contains(backgroundGOCurr)) //Needs material replacement
{
needMaterialReplacement = true;
isMaterialReplaced = false;
break;
}
}
EditorGUILayout.HelpBox("The blending mode of the background UI shader will be changed in order to ignore the background color of the camera.", MessageType.Info);
}
else
{
isCurrentBackgroundGOValid = false;
if (backgroundUINotFoundError)
{
EditorGUILayout.HelpBox("The background object you are trying to assign does not contain a UI Component.", MessageType.Error);
}
if (backgroundObjectNotChildError)
{
EditorGUILayout.HelpBox("The background object you are trying to assign is not under the current Canvas.", MessageType.Error);
}
if (GUILayout.Button("Revert Background GameObjects"))
{
targetLayerCanvasUI.backgroundGO = validbackgroundGO;
}
}
}
}
EditorGUILayout.PropertyField(Property_CameraBGColor, Label_CameraBGColor);
serializedObject.ApplyModifiedProperties();
//Check the material config of the UI elements
foreach (Graphic graphicComponent in graphicComponents)
{
if (graphicComponent.material == null || graphicComponent.material == graphicComponent.defaultMaterial)
{
needMaterialReplacement = true;
isMaterialReplaced = false;
break;
}
}
if (needMaterialReplacement || !isMaterialReplaced)
{
EditorGUILayout.HelpBox("The current material configurations of the UI elements will lead to incorrect alpha blending.\n" +
"Replace the materials to yield better visual results.", MessageType.Error);
if (GUILayout.Button("Replace UI Materials"))
{
targetLayerCanvasUI.ReplaceUIMaterials();
isMaterialReplaced = true;
}
}
//Check if current selected layer is rendered by main camera
if (Camera.main != null)
{
if ((Camera.main.cullingMask & (1 << targetLayerCanvasUI.gameObject.layer)) != 0)
{
EditorGUILayout.HelpBox("Currently selected layer: " + LayerMask.LayerToName(targetLayerCanvasUI.gameObject.layer) + "\nThis layer is not culled by the Main Camera.\nSelect a layer that will not be rendered by the Main Camera and apply it to all child objects.", MessageType.Error);
//TODO: Add Auto Layer button
if (GUILayout.Button("Auto Select Layer"))
{
// Open tag manager
SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
// Layers Property
SerializedProperty layersProp = tagManager.FindProperty("layers");
//Check if the layer CompositionLayerUICanvas exists
bool layerExists = false, firstEmptyLayerFound = false;
int emptyLayerIndex = 0;
for (int i = 0; i < layersProp.arraySize; i++)
{
if (layersProp.GetArrayElementAtIndex(i).stringValue == layerNameString)
{
layerExists = true;
ApplyLayerToGameObjectRecursive(targetLayerCanvasUI.gameObject, i);
break;
}
else if (layersProp.GetArrayElementAtIndex(i).stringValue == "")
{
if (!firstEmptyLayerFound) //Remember the index of the first empty layer
{
firstEmptyLayerFound = true;
emptyLayerIndex = i;
}
}
}
if (!layerExists) //Create layer and apply it
{
layersProp.GetArrayElementAtIndex(emptyLayerIndex).stringValue = layerNameString;
ApplyLayerToGameObjectRecursive(targetLayerCanvasUI.gameObject, emptyLayerIndex);
tagManager.ApplyModifiedProperties();
}
}
}
}
else
{
EditorGUILayout.HelpBox("Main Camera not found, and hence cannot confirm the status of its Culling Mask.\nMake sure that the Main Camera does not draw the " + LayerMask.LayerToName(targetLayerCanvasUI.gameObject.layer) + " layer." , MessageType.Warning);
}
EditorGUILayout.PropertyField(Property_CompositionDepth, Label_CompositionDepth);
serializedObject.ApplyModifiedProperties();
EditorGUILayout.PropertyField(Property_RenderPriority, Label_RenderPriority);
serializedObject.ApplyModifiedProperties();
EditorGUILayout.PropertyField(Property_TrackingOrigin, Label_TrackingOrigin);
serializedObject.ApplyModifiedProperties();
}
private void ApplyLayerToGameObjectRecursive(GameObject targetGO, int layerID)
{
if (targetGO.transform.childCount > 0)
{
for (int i=0; i<targetGO.transform.childCount; i++)
{
ApplyLayerToGameObjectRecursive(targetGO.transform.GetChild(i).gameObject, layerID);
}
}
targetGO.layer = layerID;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 222aadbc313466a41bafc2f46013fe3d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,166 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
public static class PackageManagerHelper
{
private static bool s_wasPreparing;
private static bool m_wasAdded;
private static bool s_wasRemoved;
private static ListRequest m_listRequest;
private static AddRequest m_addRequest;
private static RemoveRequest m_removeRequest;
private static string s_fallbackIdentifier;
public static bool isPreparingList
{
get
{
if (m_listRequest == null) { return s_wasPreparing = true; }
switch (m_listRequest.Status)
{
case StatusCode.InProgress:
return s_wasPreparing = true;
case StatusCode.Failure:
if (!s_wasPreparing)
{
Debug.LogError("Something wrong when adding package to list. error:" + m_listRequest.Error.errorCode + "(" + m_listRequest.Error.message + ")");
}
break;
case StatusCode.Success:
break;
}
return s_wasPreparing = false;
}
}
public static bool isAddingToList
{
get
{
if (m_addRequest == null) { return m_wasAdded = false; }
switch (m_addRequest.Status)
{
case StatusCode.InProgress:
return m_wasAdded = true;
case StatusCode.Failure:
if (!m_wasAdded)
{
AddRequest request = m_addRequest;
m_addRequest = null;
if (string.IsNullOrEmpty(s_fallbackIdentifier))
{
Debug.LogError("Something wrong when adding package to list. error:" + request.Error.errorCode + "(" + request.Error.message + ")");
}
else
{
Debug.Log("Failed to install package: \"" + request.Error.message + "\". Retry with fallback identifier \"" + s_fallbackIdentifier + "\"");
AddToPackageList(s_fallbackIdentifier);
}
s_fallbackIdentifier = null;
}
break;
case StatusCode.Success:
if (!m_wasAdded)
{
m_addRequest = null;
s_fallbackIdentifier = null;
ResetPackageList();
}
break;
}
return m_wasAdded = false;
}
}
public static bool isRemovingFromList
{
get
{
if (m_removeRequest == null) { return s_wasRemoved = false; }
switch (m_removeRequest.Status)
{
case StatusCode.InProgress:
return s_wasRemoved = true;
case StatusCode.Failure:
if (!s_wasRemoved)
{
if (m_removeRequest != null) { Debug.LogError("Something wrong when removing package from list. error:" + m_removeRequest.Error.errorCode + "(" + m_removeRequest.Error.message + ")"); }
var request = m_removeRequest;
m_removeRequest = null;
}
break;
case StatusCode.Success:
if (!s_wasRemoved)
{
m_removeRequest = null;
ResetPackageList();
}
break;
}
return s_wasRemoved = false;
}
}
public static void PreparePackageList()
{
if (m_listRequest != null) { return; }
m_listRequest = Client.List(true, true);
}
public static void ResetPackageList()
{
s_wasPreparing = false;
m_listRequest = null;
}
public static bool IsPackageInList(string name, out UnityEditor.PackageManager.PackageInfo packageInfo)
{
packageInfo = null;
if (m_listRequest == null || m_listRequest.Result == null) return false;
foreach (var package in m_listRequest.Result)
{
if (package.name.Equals(name))
{
packageInfo = package;
return true;
}
}
return false;
}
public static void AddToPackageList(string identifier, string fallbackIdentifier = null)
{
Debug.Assert(m_addRequest == null);
m_addRequest = Client.Add(identifier);
s_fallbackIdentifier = fallbackIdentifier;
}
public static void RemovePackage(string identifier)
{
Debug.Assert(m_removeRequest == null);
m_removeRequest = Client.Remove(identifier);
}
public static PackageCollection GetPackageList()
{
if (m_listRequest == null || m_listRequest.Result == null)
{
return null;
}
return m_listRequest.Result;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43952515f295bac4385e71851692047d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5f1198e3724eb5b44a705edc6d6bae06
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using UnityEngine;
#if UNITY_EDITOR
namespace VIVE.OpenXR.Editor
{
[Serializable]
public class PreferenceAvatarAsset : ScriptableObject
{
public const string AssetPath = "Assets/VIVE/OpenXR/Preferences/PreferenceAvatarAsset.asset";
// VRM constants
public const string kVrm0Package = "UniVRM-0.109.0_7aff.unitypackage";
public const string kVrm0Asset = "Assets/VRM.meta";
public const string kVrm1Package = "VRM-0.109.0_7aff.unitypackage";
public const string kVrm1Asset = "Assets/VRM10.meta";
public bool SupportVrm0 = false;
public bool SupportVrm1 = false;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 94f6766384418a0418eb5ebdb371be20
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,301 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
using System.IO;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEditor.XR.Management.Metadata;
namespace VIVE.OpenXR.Editor
{
[InitializeOnLoad]
public static class ViveOpenXRPreference
{
#region Log
static StringBuilder m_sb = null;
static StringBuilder sb {
get {
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
const string LOG_TAG = "VIVE.OpenXR.Editor.ViveOpenXRPreference";
static void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
static void ERROR(StringBuilder msg) { Debug.LogErrorFormat("{0} {1}", LOG_TAG, msg); }
#endregion
static ViveOpenXRPreference()
{
EditorApplication.update += OnUpdate;
}
#region Scripting Symbols
internal struct ScriptingDefinedSettings
{
public string[] scriptingDefinedSymbols;
public BuildTargetGroup[] targetGroups;
public ScriptingDefinedSettings(string[] symbols, BuildTargetGroup[] groups)
{
scriptingDefinedSymbols = symbols;
targetGroups = groups;
}
}
const string DEFINE_USE_VRM_0_x = "USE_VRM_0_x";
static readonly ScriptingDefinedSettings m_ScriptDefineSettingVrm0 = new ScriptingDefinedSettings(
new string[] { DEFINE_USE_VRM_0_x, },
new BuildTargetGroup[] { BuildTargetGroup.Android, }
);
static void AddScriptingDefineSymbols(ScriptingDefinedSettings setting)
{
for (int group_index = 0; group_index < setting.targetGroups.Length; group_index++)
{
var group = setting.targetGroups[group_index];
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
List<string> allDefines = definesString.Split(';').ToList();
for (int symbol_index = 0; symbol_index < setting.scriptingDefinedSymbols.Length; symbol_index++)
{
if (!allDefines.Contains(setting.scriptingDefinedSymbols[symbol_index]))
{
sb.Clear().Append("AddDefineSymbols() ").Append(setting.scriptingDefinedSymbols[symbol_index]).Append(" to group ").Append(group); DEBUG(sb);
allDefines.Add(setting.scriptingDefinedSymbols[symbol_index]);
}
else
{
sb.Clear().Append("AddDefineSymbols() ").Append(setting.scriptingDefinedSymbols[symbol_index]).Append(" already existed."); DEBUG(sb);
}
}
PlayerSettings.SetScriptingDefineSymbolsForGroup(
group,
string.Join(";", allDefines.ToArray())
);
}
}
static void RemoveScriptingDefineSymbols(ScriptingDefinedSettings setting)
{
for (int group_index = 0; group_index < setting.targetGroups.Length; group_index++)
{
var group = setting.targetGroups[group_index];
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
List<string> allDefines = definesString.Split(';').ToList();
for (int symbol_index = 0; symbol_index < setting.scriptingDefinedSymbols.Length; symbol_index++)
{
if (allDefines.Contains(setting.scriptingDefinedSymbols[symbol_index]))
{
sb.Clear().Append("RemoveDefineSymbols() ").Append(setting.scriptingDefinedSymbols[symbol_index]).Append(" from group ").Append(group); DEBUG(sb);
allDefines.Remove(setting.scriptingDefinedSymbols[symbol_index]);
}
else
{
sb.Clear().Append("RemoveDefineSymbols() ").Append(setting.scriptingDefinedSymbols[symbol_index]).Append(" already existed."); DEBUG(sb);
}
}
PlayerSettings.SetScriptingDefineSymbolsForGroup(
group,
string.Join(";", allDefines.ToArray())
);
}
}
static bool HasScriptingDefineSymbols(ScriptingDefinedSettings setting)
{
for (int group_index = 0; group_index < setting.targetGroups.Length; group_index++)
{
var group = setting.targetGroups[group_index];
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
List<string> allDefines = definesString.Split(';').ToList();
for (int symbol_index = 0; symbol_index < setting.scriptingDefinedSymbols.Length; symbol_index++)
{
if (!allDefines.Contains(setting.scriptingDefinedSymbols[symbol_index]))
{
return false;
}
}
}
return true;
}
const string XR_LOADER_OPENXR_NAME = "UnityEngine.XR.OpenXR.OpenXRLoader";
internal static bool ViveOpenXRAndroidAssigned { get { return XRPackageMetadataStore.IsLoaderAssigned(XR_LOADER_OPENXR_NAME, BuildTargetGroup.Android); } }
static PreferenceAvatarAsset m_AssetAvatar = null;
static void CheckPreferenceAssets()
{
if (File.Exists(PreferenceAvatarAsset.AssetPath))
{
m_AssetAvatar = AssetDatabase.LoadAssetAtPath(PreferenceAvatarAsset.AssetPath, typeof(PreferenceAvatarAsset)) as PreferenceAvatarAsset;
}
else
{
string folderPath = PreferenceAvatarAsset.AssetPath.Substring(0, PreferenceAvatarAsset.AssetPath.LastIndexOf('/'));
DirectoryInfo folder = Directory.CreateDirectory(folderPath);
sb.Clear().Append("CheckPreferenceAssets() Creates folder: Assets/").Append(folder.Name); DEBUG(sb);
m_AssetAvatar = ScriptableObject.CreateInstance(typeof(PreferenceAvatarAsset)) as PreferenceAvatarAsset;
m_AssetAvatar.SupportVrm0 = false;
m_AssetAvatar.SupportVrm1 = false;
sb.Clear().Append("CheckPreferenceAssets() Creates the asset: ").Append(PreferenceAvatarAsset.AssetPath); DEBUG(sb);
AssetDatabase.CreateAsset(m_AssetAvatar, PreferenceAvatarAsset.AssetPath);
}
}
static int checkPreferenceAssetsFrame = 0;
static void OnUpdate()
{
if (!ViveOpenXRAndroidAssigned) { return; }
checkPreferenceAssetsFrame++;
checkPreferenceAssetsFrame %= 1200; // 10s
if (checkPreferenceAssetsFrame != 0) { return; }
CheckPreferenceAssets();
if (m_AssetAvatar)
{
// Adds the script symbol if VRM0 is imported.
if (File.Exists(PreferenceAvatarAsset.kVrm0Asset))
{
if (!HasScriptingDefineSymbols(m_ScriptDefineSettingVrm0))
{
sb.Clear().Append("OnUpdate() Adds m_ScriptDefineSettingVrm0."); DEBUG(sb);
AddScriptingDefineSymbols(m_ScriptDefineSettingVrm0);
}
m_AssetAvatar.SupportVrm0 = true;
}
else
{
if (HasScriptingDefineSymbols(m_ScriptDefineSettingVrm0))
{
sb.Clear().Append("OnUpdate() Removes m_ScriptDefineSettingVrm0."); DEBUG(sb);
RemoveScriptingDefineSymbols(m_ScriptDefineSettingVrm0);
}
m_AssetAvatar.SupportVrm0 = false;
}
m_AssetAvatar.SupportVrm1 = File.Exists(PreferenceAvatarAsset.kVrm1Asset);
}
}
#endregion
#region Preferences
const string kPreferenceName = "VIVE OpenXR";
private static GUIContent m_Vrm0Option = new GUIContent("VRM 0", "Avatar format.");
private static GUIContent m_Vrm1Option = new GUIContent("VRM 1", "Avatar format.");
internal static void ImportModule(string packagePath, bool interactive = false)
{
string target = Path.Combine("Packages/com.htc.upm.vive.openxr/UnityPackages~", packagePath);
sb.Clear().Append("ImportModule: " + target); DEBUG(sb);
AssetDatabase.ImportPackage(target, interactive);
}
static bool avatarOption = true;
#pragma warning disable 0618
[PreferenceItem(kPreferenceName)]
#pragma warning restore 0618
private static void OnPreferencesGUI()
{
if (EditorApplication.isCompiling)
{
EditorGUILayout.LabelField("Compiling...");
return;
}
if (PackageManagerHelper.isAddingToList)
{
EditorGUILayout.LabelField("Installing packages...");
return;
}
if (PackageManagerHelper.isRemovingFromList)
{
EditorGUILayout.LabelField("Removing packages...");
return;
}
PackageManagerHelper.PreparePackageList();
if (PackageManagerHelper.isPreparingList)
{
EditorGUILayout.LabelField("Checking Packages...");
return;
}
CheckPreferenceAssets();
GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.label);
sectionTitleStyle.fontSize = 16;
sectionTitleStyle.richText = true;
sectionTitleStyle.fontStyle = FontStyle.Bold;
#region Avatar
GUILayout.BeginHorizontal();
GUILayout.Space(10);
GUILayout.Label("Avatar", sectionTitleStyle);
GUILayout.EndHorizontal();
GUIStyle foldoutStyle = EditorStyles.foldout;
foldoutStyle.fontSize = 14;
foldoutStyle.fontStyle = FontStyle.Normal;
GUILayout.BeginHorizontal();
GUILayout.Space(20);
avatarOption = EditorGUILayout.Foldout(avatarOption, "Supported Format", foldoutStyle);
GUILayout.EndHorizontal();
foldoutStyle.fontSize = 12;
foldoutStyle.fontStyle = FontStyle.Normal;
if (m_AssetAvatar && avatarOption)
{
/// VRM 0
GUILayout.Space(5);
GUILayout.BeginHorizontal();
GUILayout.Space(35);
if (!m_AssetAvatar.SupportVrm0)
{
bool toggled = EditorGUILayout.ToggleLeft(m_Vrm0Option, false, GUILayout.Width(230f));
if (toggled)
{
sb.Clear().Append("OnPreferencesGUI() Adds ").Append(PreferenceAvatarAsset.kVrm0Package); DEBUG(sb);
ImportModule(PreferenceAvatarAsset.kVrm0Package);
}
}
else
{
EditorGUILayout.ToggleLeft(m_Vrm0Option, true, GUILayout.Width(230f));
}
GUILayout.EndHorizontal();
/// VRM 1
GUILayout.Space(5);
GUILayout.BeginHorizontal();
GUILayout.Space(35);
if (!m_AssetAvatar.SupportVrm1)
{
bool toggled = EditorGUILayout.ToggleLeft(m_Vrm1Option, false, GUILayout.Width(230f));
if (toggled)
{
sb.Clear().Append("OnPreferencesGUI() Adds ").Append(PreferenceAvatarAsset.kVrm1Package); DEBUG(sb);
ImportModule(PreferenceAvatarAsset.kVrm1Package);
}
}
else
{
EditorGUILayout.ToggleLeft(m_Vrm1Option, true, GUILayout.Width(230f));
}
GUILayout.EndHorizontal();
}
#endregion
}
#endregion
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73bdd0b88ffae0e43a3a498347e6dea4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
{
"name": "VIVE.OpenXR.Editor",
"rootNamespace": "",
"references": [
"VIVE.OpenXR",
"Unity.XR.OpenXR",
"Unity.XR.OpenXR.Editor",
"Unity.XR.Management.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d6b33c0ff458eb344807c1608836e334
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEditor;
using VIVE.OpenXR.Feature;
namespace VIVE.OpenXR.Editor
{
[CustomEditor(typeof(ViveAnchor))]
internal class ViveAnchorEditor : UnityEditor.Editor
{
private SerializedProperty enablePersistedAnchor;
void OnEnable()
{
enablePersistedAnchor = serializedObject.FindProperty("enablePersistedAnchor");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(enablePersistedAnchor);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9094698271e2abb4ab295256548772c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
#if UNITY_EDITOR
using UnityEditor;
namespace VIVE.OpenXR.CompositionLayer
{
[CustomEditor(typeof(ViveCompositionLayer))]
internal class ViveCompositionLayerEditor : UnityEditor.Editor
{
private SerializedProperty enableAutoFallback;
void OnEnable()
{
enableAutoFallback = serializedObject.FindProperty("enableAutoFallback");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(enableAutoFallback);
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a4e5396be5abca4ab71bc9a8fd60e40
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
namespace VIVE.OpenXR.Editor
{
public class ViveMenu : UnityEditor.Editor
{
private const string kMenuXR = "VIVE/XR/Convert Main Camera to ViveRig";
[MenuItem(kMenuXR, priority = 101)]
private static void ConvertToViveRig()
{
// 1. Removes default Camera
Camera cam = FindObjectOfType<Camera>();
if (cam != null && cam.transform.parent == null)
{
Debug.Log("ConvertToViveRig() remove " + cam.gameObject.name);
DestroyImmediate(cam.gameObject);
}
// 2. Loads ViveRig
if (GameObject.Find("ViveRig") == null && GameObject.Find("ViveRig(Clone)") == null)
{
GameObject prefab = Resources.Load<GameObject>("Prefabs/ViveRig");
if (prefab != null)
{
Debug.Log("ConvertToViveRig() load " + prefab.name);
GameObject inst = Instantiate(prefab, null);
if (inst != null)
{
inst.name = "ViveRig";
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f78968df8bc5794393fb2016e223a6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,64 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEditor;
using UnityEngine;
using VIVE.OpenXR.Feature;
namespace VIVE.OpenXR.Editor
{
[CustomEditor(typeof(ViveMockRuntime))]
internal class ViveMockRuntimeEditor : UnityEditor.Editor
{
private SerializedProperty enableFuture;
private SerializedProperty enableAnchor;
void OnEnable()
{
enableFuture = serializedObject.FindProperty("enableFuture");
enableAnchor = serializedObject.FindProperty("enableAnchor");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Show a text field for description
EditorGUILayout.HelpBox("VIVE's mock runtime. Used with OpenXR MockRuntime to test unsupported extensions and features on Editor.", MessageType.Info);
if (GUILayout.Button("Install MockRuntime Library")) {
InstallMockRuntimeLibrary();
}
// check if changed
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(enableFuture);
if (EditorGUI.EndChangeCheck()) {
if (!enableFuture.boolValue) {
enableAnchor.boolValue = false;
}
}
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(enableAnchor);
if (EditorGUI.EndChangeCheck()) {
if (enableAnchor.boolValue) {
enableFuture.boolValue = true;
}
}
serializedObject.ApplyModifiedProperties();
}
public void InstallMockRuntimeLibrary() {
string sourcePathName = "Packages/com.htc.upm.vive.openxr/MockRuntime~/Win64/ViveMockRuntime.dll";
string destPath = "Assets/Plugins/Win64";
string destPathName = "Assets/Plugins/Win64/ViveMockRuntime.dll";
// check if the folder exists. If not, create it.
if (!System.IO.Directory.Exists(destPath)) {
System.IO.Directory.CreateDirectory(destPath);
}
FileUtil.CopyFileOrDirectory(sourcePathName, destPathName);
AssetDatabase.Refresh();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eedb4211aafd2cb4bae86fcc0e948f72
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
// Copyright HTC Corporation All Rights Reserved.
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
namespace VIVE.OpenXR
{
[OpenXRFeatureSet(
FeatureIds = new string[] {
VIVEFocus3Feature.featureId,
VIVEFocus3Profile.featureId,
Hand.ViveHandTracking.featureId,
"vive.openxr.feature.compositionlayer",
"vive.openxr.feature.compositionlayer.cylinder",
"vive.openxr.feature.compositionlayer.colorscalebias",
CompositionLayer.ViveCompositionLayerEquirect.featureId,
Tracker.ViveWristTracker.featureId,
Hand.ViveHandInteraction.featureId,
"vive.openxr.feature.foveation",
FacialTracking.ViveFacialTracking.featureId,
PlaneDetection.VivePlaneDetection.featureId,
VivePathEnumeration.featureId,
Feature.ViveAnchor.featureId,
DisplayRefreshRate.ViveDisplayRefreshRate.featureId,
Passthrough.VivePassthrough.featureId,
FirstPersonObserver.ViveFirstPersonObserver.FeatureId,
SecondaryViewConfiguration.ViveSecondaryViewConfiguration.FeatureId,
UserPresence.ViveUserPresence.featureId,
CompositionLayer.ViveCompositionLayerExtraSettings.featureId,
FrameSynchronization.ViveFrameSynchronization.featureId,
EyeTracker.ViveEyeTracker.featureId,
Feature.ViveMockRuntime.featureId,
Interaction.ViveInteractions.featureId,
},
UiName = "VIVE XR Support",
Description = "Necessary to deploy an VIVE XR compatible app.",
FeatureSetId = "com.htc.vive.openxr.featureset.vivexr",
#if UNITY_ANDROID
DefaultFeatureIds = new string[] { VIVEFocus3Feature.featureId, VIVEFocus3Profile.featureId, },
#endif
SupportedBuildTargets = new BuildTargetGroup[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone }
)]
sealed class ViveOpenXRFeatureSet { }
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6fe9adb019aff2e419eee912e8171cf9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
using System;
using System.Reflection;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR;
using static VIVE.OpenXR.VIVEFocus3Feature;
namespace VIVE.OpenXR.Editor
{
public class ViveSpectatorCameraProcess : OpenXRFeatureBuildHooks
{
public override int callbackOrder => 1;
public override Type featureType => typeof(VIVEFocus3Feature);
/// <summary>
/// Enable or disable the "First Person Observer" extension according to the Spectator Camera Feature.
/// </summary>
/// <param name="enable">Type True if Spectator Camera Feature is enabled. Otherwise, type False.</param>
private static void SetFirstPersonObserver(in bool enable)
{
var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android);
foreach (OpenXRFeature feature in settings.GetFeatures<OpenXRFeature>())
{
FieldInfo fieldInfoOpenXrExtensionStrings = typeof(OpenXRFeature).GetField(
"openxrExtensionStrings",
BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfoOpenXrExtensionStrings != null)
{
var openXrExtensionStringsArray =
((string)fieldInfoOpenXrExtensionStrings.GetValue(feature)).Split(' ');
foreach (var stringItem in openXrExtensionStringsArray)
{
if (string.IsNullOrEmpty(stringItem))
{
continue;
}
if (!string.Equals(stringItem, FirstPersonObserver.ViveFirstPersonObserver.OPEN_XR_EXTENSION_STRING))
{
continue;
}
feature.enabled = enable;
return;
}
}
}
}
#region The callbacks during the build process when your OpenXR Extension is enabled.
protected override void OnPreprocessBuildExt(BuildReport report)
{
if (IsViveSpectatorCameraEnabled())
{
SetFirstPersonObserver(true);
UnityEngine.Debug.Log("Enable \"First Person Observer\" extension due to the Spectator Camera Feature.");
}
else
{
SetFirstPersonObserver(false);
UnityEngine.Debug.Log("Disable \"First Person Observer\" extension because Spectator Camera Feature is closed.");
}
}
protected override void OnPostGenerateGradleAndroidProjectExt(string path)
{
}
protected override void OnPostprocessBuildExt(BuildReport report)
{
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4fcb7e5a984acb64bb9221b9b05c0517
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,4 @@
Copyright © HTC Corporation, LLC and its affiliates. All rights reserved.
Your use of this SDK, sample, or tool is subject to HTC VIVE SDK License Agreement, available at https://developer.vive.com/resources/downloads/licenses-and-agreements/

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ecdb711833cec9e49b0c978f3f9aada1
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f717cf3d3557f7ff8e617804558b27aa828dc2180f57a7311fd19a3a0a66c536
size 182784

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2dc6996d92cd4d2419c9056b63e098d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c51db0e4c8f60a8408a42a24c814c91f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 371f6068195afb245930c68ab49cbbac
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,70 @@
fileFormatVersion: 2
guid: dc0da4ddf5dc37048a2731233e55e0bc
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARM64
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,70 @@
fileFormatVersion: 2
guid: 67f3377461b49714d814642f3a49b69d
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARM64
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,70 @@
fileFormatVersion: 2
guid: f62b07ae6f5a8984b87047c2172cb605
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: x86
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: x86_64
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d55a23fb1f9c7b0479d1f15716620072
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,52 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
namespace VIVE.OpenXR.Feature
{
public interface IViveFeatureWrapper
{
/// <summary>
/// OnInstanceCreate might be called multiple times. Because many features might be using the same instance.
/// </summary>
/// <param name="xrInstance"></param>
/// <param name="xrGetInstanceProcAddr"></param>
/// <returns></returns>
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddr);
/// <summary>
/// OnInstanceDestroy might be called multiple times. Because many features might be using the same instance.
/// </summary>
public void OnInstanceDestroy();
}
public class ViveFeatureWrapperBase<T> where T : ViveFeatureWrapperBase<T>, new()
{
private static readonly Lazy<T> lazyInstance = new Lazy<T>(() => new T());
public static T Instance => lazyInstance.Value;
// Set true in yourfeature's OnInstanceCreate
public bool IsInited { get; protected set; } = false;
/// <summary>
/// If the feature is inited not successfully, Set this true. Use to avoid multiple inits.
/// </summary>
public bool TryInited { get; protected set; } = false;
public OpenXRHelper.xrGetInstanceProcAddrDelegate xrGetInstanceProcAddr;
/// <summary>
/// Complete the xrGetInstanceProcAddr by set the pointer received in OnInstanceCreate
/// </summary>
/// <param name="intPtr"></param>
public void SetGetInstanceProcAddrPtr(IntPtr intPtr)
{
if (intPtr == null || intPtr == IntPtr.Zero)
throw new Exception("xrGetInstanceProcAddr is null");
xrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrGetInstanceProcAddrDelegate>(intPtr);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a27dc5505cdb29347aeda46676cedaa8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,228 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.Profiling;
namespace VIVE.OpenXR
{
internal static class MemoryTools
{
/// <summary>
/// Make sure the input ptr is a OpenXR XrBaseStructure derived struct.
/// </summary>
/// <param name="ptr">the struct to get its next.</param>
/// <returns>the next's value</returns>
public static unsafe IntPtr GetNext(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
return IntPtr.Zero;
//Profiler.BeginSample("GetNext");
XrBaseStructure* ptrToStruct = (XrBaseStructure*)ptr.ToPointer();
//Profiler.EndSample();
return ptrToStruct->next;
}
/// <summary>
/// Make sure the input ptr is a OpenXR XrBaseStructure derived struct.
/// </summary>
/// <param name="ptr">the struct to get its type</param>
/// <returns>the struct's type</returns>
public static unsafe XrStructureType GetType(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
throw new Exception("The input pointer is null.");
//Profiler.BeginSample("GetType");
XrBaseStructure* ptrToStruct = (XrBaseStructure*)ptr.ToPointer();
//Profiler.EndSample();
return ptrToStruct->type;
}
public static unsafe XrBaseStructure ToBaseStructure(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
throw new Exception("The input pointer is null.");
//Profiler.BeginSample("ToBaseStructure");
XrBaseStructure* ptrToStruct = (XrBaseStructure*)ptr.ToPointer();
//Profiler.EndSample();
return *ptrToStruct;
}
public static unsafe T PtrToStructure<T>(IntPtr ptr) where T : unmanaged
{
//Profiler.BeginSample("PtrToStructure");
// Not to use Marshal.PtrToStructure<T> because it is slow.
T t = default; // Use new T() will cause GC alloc.
Buffer.MemoryCopy((void*)ptr, &t, sizeof(T), sizeof(T));
//Profiler.EndSample();
return t;
}
public static unsafe void PtrToStructure<T>(IntPtr ptr, ref T t) where T : unmanaged
{
//Profiler.BeginSample("PtrToStructure");
fixed (T* destinationPtr = &t)
{
Buffer.MemoryCopy((void*)ptr, destinationPtr, sizeof(T), sizeof(T));
}
//Profiler.EndSample();
}
public static unsafe void StructureToPtr<T>(T t, IntPtr ptr) where T : unmanaged
{
//Profiler.BeginSample("StructureToPtr");
// Not to use Marshal.StructureToPtr<T> because it is slow.
Buffer.MemoryCopy(&t, (void*)ptr, sizeof(T), sizeof(T));
//Profiler.EndSample();
}
/// <summary>
/// Convert the enum array to IntPtr. Should call <see cref="ReleaseRawMemory(IntPtr)"/> after use.
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static unsafe IntPtr ToIntPtr<T>(T[] array) where T : Enum
{
int size = sizeof(int) * array.Length;
IntPtr ptr = Marshal.AllocHGlobal(size);
int* intPtr = (int*)ptr.ToPointer();
for (int i = 0; i < array.Length; i++)
{
// Convert enum to int. This has better performance than Convert.ToInt32.
intPtr[i] = (int)(object)array[i];
}
return ptr;
}
/// <summary>
/// Convert the struct to IntPtr. Should call <see cref="ReleaseRawMemory(IntPtr)"/> after use.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="structure"></param>
/// <returns></returns>
public static IntPtr ToIntPtr<T>(T structure) where T : struct
{
int size = Marshal.SizeOf(structure);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(structure, ptr, true);
return ptr;
}
/// <summary>
/// Make the same size raw buffer from input array.
/// </summary>
/// <typeparam name="T">Data type could be primitive type or struct. Should call <see cref="ReleaseRawMemory(IntPtr)"/> after use.</typeparam>
/// <param name="refArray">The data array</param>
/// <returns>The memory handle. Should release by <see cref="ReleaseRawMemory(IntPtr)"/></returns>
public static unsafe IntPtr MakeRawMemory<T>(T[] refArray) where T : unmanaged
{
int size = Marshal.SizeOf(typeof(T)) * refArray.Length;
return Marshal.AllocHGlobal(size);
}
/// <summary>
/// Copy the raw memory to the array. You should make sure the array has the same size as the raw memory.
/// </summary>
/// <typeparam name="T">Convert the memory to this type array.</typeparam>
/// <param name="array">The output array.</param>
/// <param name="raw">The data source in raw memory form.</param>
/// <param name="count">Specify the copy count. Count should be less than array length.</param>
public static unsafe void CopyFromRawMemory<T>(T[] array, IntPtr raw, int count = 0) where T : unmanaged
{
//Profiler.BeginSample("CopyFromRawMemory");
int N = array.Length;
if (count > 0 && count < array.Length)
N = count;
int step = sizeof(T);
int bufferSize = step * N;
// Pin array's address. Prevent GC move it.
fixed (T* destPtr = array)
{
T* sourcePtr = (T*)raw.ToPointer();
Buffer.MemoryCopy(sourcePtr, destPtr, bufferSize, bufferSize);
}
//Profiler.EndSample();
}
/// <summary>
/// Copy all raw memory to the array. This has higher performance than <see cref="CopyFromRawMemory"/>.
/// Use this method if you have frequent update requirements.
/// You need prepare a byte buffer to store the raw memory. The byte buffer size should be tSize * array.Length.
/// tSize is used for checking the byte buffer size. If tSize is 0, it will use Marshal.SizeOf(typeof(T)).
/// You can save the size at your size to avoid the Marshal.Sizeof(typeof(T)) call repeatedly.
/// </summary>
/// <typeparam name="T">Convert the memory to this type array.</typeparam>
/// <param name="array">The output array.</param>
/// <param name="raw">The data source in raw memory form.</param>
public static unsafe void CopyAllFromRawMemory<T>(T[] array, IntPtr raw) where T : unmanaged
{
#if DEBUG
if (array == null)
throw new ArgumentNullException(nameof(array), "Output array cannot be null.");
if (raw == IntPtr.Zero)
throw new ArgumentNullException(nameof(raw), "Raw memory pointer cannot be null.");
#endif
//Profiler.BeginSample("CopyAllFromRawMemory");
int elementSize = sizeof(T);
int requiredBufferSize = elementSize * array.Length;
// Pin array's address. Prevent GC move it.
fixed (T* destPtr = array)
{
T* sourcePtr = (T*)raw.ToPointer();
Buffer.MemoryCopy(sourcePtr, destPtr, requiredBufferSize, requiredBufferSize);
}
//Profiler.EndSample();
}
/// <summary>
/// Make the same size raw buffer from input array. Make sure the raw has enough size.
/// </summary>
/// <typeparam name="T">Convert this type array to raw memory.</typeparam>
/// <param name="raw">The output data in raw memory form</param>
/// <param name="array">The data source</param>
public static unsafe void CopyToRawMemory<T>(IntPtr raw, T[] array) where T : unmanaged
{
//Profiler.BeginSample("CopyToRawMemory");
int step = sizeof(T);
int bufferSize = step * array.Length;
// Pin array's address. Prevent GC move it.
fixed (T* destPtr = array)
{
void* ptr = raw.ToPointer();
Buffer.MemoryCopy(destPtr, ptr, bufferSize, bufferSize);
}
//Profiler.EndSample();
}
/// <summary>
/// Release the raw memory handle which is created by <see cref="MakeRawMemory{T}(T[])"/>
/// </summary>
/// <param name="ptr"></param>
public static void ReleaseRawMemory(IntPtr ptr)
{
Marshal.FreeHGlobal(ptr);
}
/// <summary>
/// Find a pointer in the next chain. Make sure the input next pointer is a OpenXR XrBaseStructure derived struct.
/// </summary>
/// <param name="target"></param>
/// <param name="next"></param>
/// <returns>true if exist</returns>
public static bool HasPtrInNextChain(IntPtr target, IntPtr next)
{
while (next != IntPtr.Zero)
{
if (next == target)
return true;
next = GetNext(next);
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a887cb158a37cf45b17458a4f27d7ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5b2125d5dc73694eaed34e97a0962e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,252 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Profiling;
namespace VIVE.OpenXR.Feature
{
/// <summary>
/// To use this wrapper, you need to call CommonWrapper.Instance.OnInstanceCreate() in your feature's OnInstanceCreate(),
/// and call CommonWrapper.Instance.OnInstanceDestroy() in your feature's OnInstanceDestroy().
///
/// Note:
/// In Standardalone's OpenXR MockRuntime, the CreateSwapchain and EnumerateSwapchainImages will work and return success,
/// but the images's native pointer will be null.
/// </summary>
internal class CommonWrapper : ViveFeatureWrapperBase<CommonWrapper>, IViveFeatureWrapper
{
const string TAG = "CommonWrapper";
OpenXRHelper.xrGetSystemPropertiesDelegate XrGetSystemProperties;
OpenXRHelper.xrCreateSwapchainDelegate XrCreateSwapchain;
OpenXRHelper.xrDestroySwapchainDelegate XrDestroySwapchain;
OpenXRHelper.xrEnumerateSwapchainFormatsDelegate XrEnumerateSwapchainFormats;
OpenXRHelper.xrEnumerateSwapchainImagesDelegate XrEnumerateSwapchainImages;
OpenXRHelper.xrWaitSwapchainImageDelegate XrWaitSwapchainImage;
OpenXRHelper.xrAcquireSwapchainImageDelegate XrAcquireSwapchainImage;
OpenXRHelper.xrReleaseSwapchainImageDelegate XrReleaseSwapchainImage;
/// <summary>
/// In feature's OnInstanceCreate(), call CommonWrapper.Instance.OnInstanceCreate() for init common APIs.
/// </summary>
/// <param name="xrInstance">Passed in feature's OnInstanceCreate.</param>
/// <param name="xrGetInstanceProcAddr">Pass OpenXRFeature.xrGetInstanceProcAddr in.</param>
/// <returns></returns>
/// <exception cref="Exception">If input data not valid.</exception>
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrPtr)
{
if (IsInited) return true;
if (TryInited) return false;
TryInited = true;
if (xrInstance == 0)
throw new Exception("CommonWrapper: xrInstance is null");
Log.D(TAG, "OnInstanceCreate()");
SetGetInstanceProcAddrPtr(xrGetInstanceProcAddrPtr);
bool ret = true;
IntPtr funcPtr = IntPtr.Zero;
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrGetSystemProperties", out XrGetSystemProperties);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrCreateSwapchain", out XrCreateSwapchain);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrDestroySwapchain", out XrDestroySwapchain);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrEnumerateSwapchainFormats", out XrEnumerateSwapchainFormats);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrEnumerateSwapchainImages", out XrEnumerateSwapchainImages);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrWaitSwapchainImage", out XrWaitSwapchainImage);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrAcquireSwapchainImage", out XrAcquireSwapchainImage);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrReleaseSwapchainImage", out XrReleaseSwapchainImage);
if (!ret)
throw new Exception("CommonWrapper: Get function pointers failed.");
IsInited = ret;
return ret;
}
/// <summary>
/// In feature's OnInstanceDestroy(), call CommonWrapper.Instance.OnInstanceDestroy() for disable common APIs.
/// </summary>
/// <returns></returns>
public void OnInstanceDestroy()
{
// Do not destroy twice
if (IsInited == false) return;
IsInited = false;
XrGetSystemProperties = null;
Log.D(TAG, "OnInstanceDestroy()");
}
public XrResult GetInstanceProcAddr(XrInstance instance, string name, out IntPtr function)
{
if (IsInited == false || xrGetInstanceProcAddr == null)
{
function = IntPtr.Zero;
return XrResult.XR_ERROR_HANDLE_INVALID;
}
return xrGetInstanceProcAddr(instance, name, out function);
}
/// <summary>
/// Helper function to get system properties. Need input your features' xrInstance and xrSystemId. Fill the system properites in next for you feature.
/// See <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystemProperties">xrGetSystemProperties</see>
/// </summary>
/// <param name="instance"></param>
/// <param name="systemId"></param>
/// <param name="properties"></param>
/// <returns></returns>
public XrResult GetSystemProperties(XrInstance instance, XrSystemId systemId, ref XrSystemProperties properties)
{
if (IsInited == false || XrGetSystemProperties == null)
{
return XrResult.XR_ERROR_HANDLE_INVALID;
}
return XrGetSystemProperties(instance, systemId, ref properties);
}
public XrResult GetProperties<T>(XrInstance instance, XrSystemId systemId, ref T featureProperty)
{
XrSystemProperties systemProperties = new XrSystemProperties();
systemProperties.type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES;
systemProperties.next = Marshal.AllocHGlobal(Marshal.SizeOf(featureProperty));
long offset = 0;
if (IntPtr.Size == 4)
offset = systemProperties.next.ToInt32();
else
offset = systemProperties.next.ToInt64();
IntPtr pdPropertiesPtr = new IntPtr(offset);
Marshal.StructureToPtr(featureProperty, pdPropertiesPtr, false);
var ret = GetSystemProperties(instance, systemId, ref systemProperties);
if (ret == XrResult.XR_SUCCESS)
{
if (IntPtr.Size == 4)
offset = systemProperties.next.ToInt32();
else
offset = systemProperties.next.ToInt64();
pdPropertiesPtr = new IntPtr(offset);
featureProperty = Marshal.PtrToStructure<T>(pdPropertiesPtr);
}
Marshal.FreeHGlobal(systemProperties.next);
return ret;
}
public XrResult CreateSwapchain(XrSession session, ref XrSwapchainCreateInfo createInfo, out XrSwapchain swapchain)
{
if (IsInited == false || XrCreateSwapchain == null)
{
swapchain = default;
return XrResult.XR_ERROR_HANDLE_INVALID;
}
return XrCreateSwapchain(session, ref createInfo, out swapchain);
}
public XrResult DestroySwapchain(XrSwapchain swapchain)
{
if (IsInited == false || XrDestroySwapchain == null)
{
return XrResult.XR_ERROR_HANDLE_INVALID;
}
return XrDestroySwapchain(swapchain);
}
public XrResult EnumerateSwapchainFormats(XrSession session, uint formatCapacityInput, ref uint formatCountOutput, ref long[] formats)
{
if (IsInited == false || XrEnumerateSwapchainFormats == null)
{
formatCountOutput = 0;
return XrResult.XR_ERROR_HANDLE_INVALID;
}
if (formatCapacityInput != 0 && (formats == null || formats.Length < formatCapacityInput))
return XrResult.XR_ERROR_SIZE_INSUFFICIENT;
if (formatCapacityInput == 0)
{
Log.D(TAG, "EnumerateSwapchainFormats(ci=" + formatCapacityInput + ")");
return XrEnumerateSwapchainFormats(session, 0, ref formatCountOutput, IntPtr.Zero);
}
else
{
Log.D(TAG, "EnumerateSwapchainFormats(ci=" + formatCapacityInput + ", formats=long[" + formats.Length + "])");
IntPtr formatsPtr = MemoryTools.MakeRawMemory(formats);
var ret = XrEnumerateSwapchainFormats(session, formatCapacityInput, ref formatCountOutput, formatsPtr);
if (ret == XrResult.XR_SUCCESS)
MemoryTools.CopyFromRawMemory(formats, formatsPtr, (int)formatCountOutput);
MemoryTools.ReleaseRawMemory(formatsPtr);
return ret;
}
}
public XrResult EnumerateSwapchainImages(XrSwapchain swapchain, uint imageCapacityInput, ref uint imageCountOutput, IntPtr imagesPtr)
{
if (IsInited == false || XrEnumerateSwapchainImages == null)
{
imageCountOutput = 0;
return XrResult.XR_ERROR_HANDLE_INVALID;
}
return XrEnumerateSwapchainImages(swapchain, imageCapacityInput, ref imageCountOutput, imagesPtr);
}
[DllImport("viveopenxr", EntryPoint = "CwAcquireSwapchainImage")]
public static extern XrResult CwAcquireSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageAcquireInfo acquireInfo, out uint index);
public XrResult AcquireSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageAcquireInfo acquireInfo, out uint index)
{
if (IsInited == false || XrAcquireSwapchainImage == null)
{
index = 0;
return XrResult.XR_ERROR_HANDLE_INVALID;
}
Profiler.BeginSample("ASW:xrAcqScImg");
var res = XrAcquireSwapchainImage(swapchain, ref acquireInfo, out index);
Profiler.EndSample();
return res;
}
[DllImport("viveopenxr", EntryPoint = "CwWaitSwapchainImage")]
public static extern XrResult CwWaitSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageWaitInfo waitInfo);
public XrResult WaitSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageWaitInfo waitInfo)
{
if (IsInited == false || XrWaitSwapchainImage == null)
{
return XrResult.XR_ERROR_HANDLE_INVALID;
}
Profiler.BeginSample("ASW:xrWaitScImg");
var res = XrWaitSwapchainImage(swapchain, ref waitInfo);
Profiler.EndSample();
return res;
}
[DllImport("viveopenxr", EntryPoint = "CwReleaseSwapchainImage")]
public static extern XrResult CwReleaseSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageReleaseInfo releaseInfo);
public XrResult ReleaseSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageReleaseInfo releaseInfo)
{
if (IsInited == false || XrReleaseSwapchainImage == null)
{
return XrResult.XR_ERROR_HANDLE_INVALID;
}
// Add Profiler
Profiler.BeginSample("ASW:xrRelScImg");
var res = XrReleaseSwapchainImage(swapchain, ref releaseInfo);
Profiler.EndSample();
return res;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b940cd65f52cd5c44bd79869c5d521b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,211 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using UnityEngine;
using UnityEngine.XR.OpenXR;
namespace VIVE.OpenXR.Feature
{
using XrFutureEXT = System.IntPtr;
/// <summary>
/// To use this wrapper,
/// 1. Add the "XR_EXT_Future" extension to the instance's enabled extensions list.
/// 2. Call FutureWrapper.Instance.OnInstanceCreate() in your feature's OnInstanceCreate().
/// 3. Call FutureWrapper.Instance.OnInstanceDestroy() in your feature's OnInstanceDestroy().
///
/// <see cref="VIVE.OpenXR.Toolkits.FutureTask.Poll"/> function helps make async Task.
/// </summary>
public class FutureWrapper : ViveFeatureWrapperBase<FutureWrapper>, IViveFeatureWrapper
{
const string TAG = "ViveFuture";
public enum XrFutureStateEXT
{
None = 0, // Not defined in extension. A default value.
Pending = 1,
Ready = 2,
MAX = 0x7FFFFFFF
}
public struct XrFuturePollInfoEXT {
public XrStructureType type; // XR_TYPE_FUTURE_POLL_INFO_EXT
public IntPtr next;
public XrFutureEXT future;
}
public struct XrFuturePollResultEXT {
public XrStructureType type; // XR_TYPE_FUTURE_POLL_RESULT_EXT
public IntPtr next;
public XrFutureStateEXT state;
}
public struct XrFutureCancelInfoEXT
{
public XrStructureType type; // XR_TYPE_FUTURE_CANCEL_INFO_EXT
public IntPtr next;
public XrFutureEXT future;
}
public struct XrFutureCompletionBaseHeaderEXT
{
public XrStructureType type; // XR_TYPE_FUTURE_COMPLETION_EXT
public IntPtr next;
public XrResult futureResult;
}
public struct XrFutureCompletionEXT
{
public XrStructureType type; // XR_TYPE_FUTURE_COMPLETION_EXT
public IntPtr next;
public XrResult futureResult;
}
public delegate XrResult XrPollFutureEXTDelegate(XrInstance instance, ref XrFuturePollInfoEXT pollInfo, out XrFuturePollResultEXT pollResult);
public delegate XrResult XrCancelFutureEXTDelegate(XrInstance instance, ref XrFutureCancelInfoEXT cancelInfo);
XrPollFutureEXTDelegate XrPollFutureEXT;
XrCancelFutureEXTDelegate XrCancelFutureEXT;
XrInstance xrInstance;
/// <summary>
/// Features should call FutureWrapper.Instance.OnInstanceCreate() in their OnInstanceCreate().
/// </summary>
/// <param name="xrInstance"></param>
/// <param name="xrGetInstanceProcAddrPtr"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrPtr)
{
if (IsInited) return true;
if (TryInited) return false;
TryInited = true;
if (xrInstance == null)
throw new Exception("FutureWrapper: xrInstance is null");
this.xrInstance = xrInstance;
if (xrGetInstanceProcAddrPtr == null)
throw new Exception("FutureWrapper: xrGetInstanceProcAddr is null");
SetGetInstanceProcAddrPtr(xrGetInstanceProcAddrPtr);
Log.D(TAG, "OnInstanceCreate()");
bool hasFuture = OpenXRRuntime.IsExtensionEnabled("XR_EXT_future");
if (!hasFuture)
{
Log.E(TAG, "FutureWrapper: XR_EXT_future is not enabled. Check your feature's kOpenxrExtensionString.");
return false;
}
bool ret = true;
IntPtr funcPtr = IntPtr.Zero;
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrPollFutureEXT", out XrPollFutureEXT);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrCancelFutureEXT", out XrCancelFutureEXT);
if (!ret)
{
Log.E(TAG,"FutureWrapper: Failed to get function pointer.");
return false;
}
IsInited = ret;
return ret;
}
public void OnInstanceDestroy()
{
Log.D(TAG, "OnInstanceDestroy()");
IsInited = false;
XrPollFutureEXT = null;
XrCancelFutureEXT = null;
xrInstance = 0;
}
/// <summary>
/// Used to get the state of a future. If Ready, Call complete functions to get the result.
/// </summary>
/// <param name="pollInfo"></param>
/// <param name="pollResult"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public XrResult PollFuture(ref XrFuturePollInfoEXT pollInfo, out XrFuturePollResultEXT pollResult)
{
pollResult= new XrFuturePollResultEXT()
{
type = XrStructureType.XR_TYPE_FUTURE_POLL_RESULT_EXT,
next = IntPtr.Zero,
state = XrFutureStateEXT.None
};
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
return XrPollFutureEXT(xrInstance, ref pollInfo, out pollResult);
}
/// <summary>
/// Used to get the state of a future. If Ready, Call complete functions to get the result.
/// </summary>
/// <param name="future"></param>
/// <param name="pollResult"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public XrResult PollFuture(XrFutureEXT future, out XrFuturePollResultEXT pollResult)
{
pollResult = new XrFuturePollResultEXT()
{
type = XrStructureType.XR_TYPE_FUTURE_POLL_RESULT_EXT,
next = IntPtr.Zero,
state = XrFutureStateEXT.None
};
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
XrFuturePollInfoEXT pollInfo = new XrFuturePollInfoEXT()
{
type = XrStructureType.XR_TYPE_FUTURE_POLL_INFO_EXT,
next = IntPtr.Zero,
future = future
};
return XrPollFutureEXT(xrInstance, ref pollInfo, out pollResult);
}
/// <summary>
/// This function cancels the future and signals that the async operation is not required.
/// After a future has been cancelled any functions using this future must return XR_ERROR_FUTURE_INVALID_EXT.
/// </summary>
/// <param name="cancelInfo"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public XrResult CancelFuture(ref XrFutureCancelInfoEXT cancelInfo)
{
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
return XrCancelFutureEXT(xrInstance, ref cancelInfo);
}
/// <summary>
/// <see cref="CancelFuture(ref XrFutureCancelInfoEXT)"/>
/// </summary>
/// <param name="future"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public XrResult CancelFuture(XrFutureEXT future)
{
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
XrFutureCancelInfoEXT cancelInfo = new XrFutureCancelInfoEXT()
{
type = XrStructureType.XR_TYPE_FUTURE_CANCEL_INFO_EXT,
next = IntPtr.Zero,
future = future
};
return XrCancelFutureEXT(xrInstance, ref cancelInfo);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8522c7af0a4127409a8800e1ddd5985
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,227 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace VIVE.OpenXR.Feature
{
/// <summary>
/// To use this wrapper, you need to call CommonWrapper.Instance.OnInstanceCreate() in your feature's OnInstanceCreate(),
/// and call CommonWrapper.Instance.OnInstanceDestroy() in your feature's OnInstanceDestroy().
/// </summary>
public class SpaceWrapper : ViveFeatureWrapperBase<SpaceWrapper>, IViveFeatureWrapper
{
const string TAG = "ViveSpaceWrapper";
public delegate XrResult DelegateXrEnumerateReferenceSpaces(XrSession session, uint spaceCapacityInput, out uint spaceCountOutput, [Out] XrReferenceSpaceType[] spaces);
delegate XrResult DelegateXrLocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location);
delegate XrResult DelegateXrDestroySpace(XrSpace space);
DelegateXrEnumerateReferenceSpaces XrEnumerateReferenceSpaces;
OpenXRHelper.xrCreateReferenceSpaceDelegate XrCreateReferenceSpace;
DelegateXrLocateSpace XrLocateSpace;
DelegateXrDestroySpace XrDestroySpace;
/// <summary>
/// Features should call ViveSpaceWrapper.Instance.OnInstanceCreate() in their OnInstanceCreate().
/// </summary>
/// <param name="xrInstance"></param>
/// <param name="GetAddr"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr GetAddr)
{
if (IsInited) return true;
if (TryInited) return false;
TryInited = true;
if (xrInstance == null)
throw new Exception("ViveSpaceWrapper: xrInstance is null");
SetGetInstanceProcAddrPtr(GetAddr);
Log.D(TAG, "OnInstanceCreate()");
bool ret = true;
IntPtr funcPtr = IntPtr.Zero;
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrEnumerateReferenceSpaces", out XrEnumerateReferenceSpaces);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrCreateReferenceSpace", out XrCreateReferenceSpace);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrLocateSpace", out XrLocateSpace);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrDestroySpace", out XrDestroySpace);
IsInited = ret;
return ret;
}
public void OnInstanceDestroy()
{
// Do not destroy twice
if (IsInited == false) return;
IsInited = false;
XrEnumerateReferenceSpaces = null;
XrCreateReferenceSpace = null;
XrLocateSpace = null;
XrDestroySpace = null;
}
/// <summary>
///
/// </summary>
/// <param name="session"></param>
/// <param name="spaceCapacityInput"></param>
/// <param name="spaceCountOutput"></param>
/// <param name="spaces"></param>
/// <returns></returns>
public XrResult EnumerateReferenceSpaces(XrSession session, int spaceCapacityInput, ref int spaceCountOutput, ref XrReferenceSpaceType[] spaces)
{
spaceCountOutput = 0;
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
if (spaceCapacityInput != 0 && spaces != null && spaces.Length < spaceCapacityInput)
return XrResult.XR_ERROR_SIZE_INSUFFICIENT;
var ret = XrEnumerateReferenceSpaces(session, (uint)spaceCapacityInput, out uint spaceCountOutputXR, spaces);
spaceCountOutput = (int)spaceCountOutputXR;
return ret;
}
/// <summary>
/// Create a reference space without create info.
/// Example:
/// CreateReferenceSpace(session, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_LOCAL, XrPosef.Identity, out space);
/// CreateReferenceSpace(session, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_STAGE, XrPosef.Identity, out space);
/// </summary>
/// <param name="session"></param>
/// <param name="referenceSpaceType"></param>
/// <param name="pose"></param>
/// <param name="space"></param>
/// <returns></returns>
public XrResult CreateReferenceSpace(XrSession session, XrReferenceSpaceType referenceSpaceType, XrPosef pose, out XrSpace space)
{
space = 0;
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
var createInfo = new XrReferenceSpaceCreateInfo();
createInfo.type = XrStructureType.XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
createInfo.next = IntPtr.Zero;
createInfo.referenceSpaceType = referenceSpaceType;
createInfo.poseInReferenceSpace = pose;
return XrCreateReferenceSpace(session, ref createInfo, out space);
}
/// <summary>
/// Create a reference space.
/// </summary>
/// <param name="session"></param>
/// <param name="createInfo"></param>
/// <param name="space"></param>
/// <returns></returns>
public XrResult CreateReferenceSpace(XrSession session, XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
{
space = 0;
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
return XrCreateReferenceSpace(session, ref createInfo, out space);
}
public XrResult LocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location)
{
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
//Debug.Log($"LocateSpace(s={space}, bs={baseSpace}, t={time}");
return XrLocateSpace(space, baseSpace, time, ref location);
}
public XrResult DestroySpace(XrSpace space)
{
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
Log.D(TAG, $"DestroySpace({space})");
return XrDestroySpace(space);
}
}
/// <summary>
/// The XrSpace's Unity wrapper. Input and output are in Unity coordinate system.
/// After use it, you should call Dispose() to release the XrSpace.
/// </summary>
public class Space : IDisposable
{
protected XrSpace space;
private bool disposed = false;
public Space(XrSpace space)
{
Log.D($"Space({space})");
this.space = space;
}
/// <summary>
/// Get the raw XrSpace. Only use it when class Space instance is alive.
/// You should not try to store this XrSpace, because it may be destroyed.
/// </summary>
/// <returns></returns>
public XrSpace GetXrSpace()
{
return space;
}
public bool GetRelatedPose(XrSpace baseSpace, XrTime time, out UnityEngine.Pose pose)
{
// If the xrBaseSpace is changed, the pose will be updated.
pose = default;
XrSpaceLocation location = new XrSpaceLocation();
location.type = XrStructureType.XR_TYPE_SPACE_LOCATION;
location.next = IntPtr.Zero;
var ret = SpaceWrapper.Instance.LocateSpace(space, baseSpace, time, ref location);
if (ret != XrResult.XR_SUCCESS)
{
//Debug.Log("Space: LocateSpace ret=" + ret);
return false;
}
//Debug.Log("Space: baseSpace=" + baseSpace + ", space=" + space + ", time=" + time + ", ret=" + ret);
//Debug.Log("Space: location.locationFlags=" + location.locationFlags);
//Debug.Log("Space: location.pose.position=" + location.pose.position.x + "," + location.pose.position.y + "," + location.pose.position.z);
//Debug.Log("Space: location.pose.orientation=" + location.pose.orientation.x + "," + location.pose.orientation.y + "," + location.pose.orientation.z + "," + location.pose.orientation.w);
if ((location.locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_VALID_BIT) > 0 &&
(location.locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) > 0)
{
pose = new Pose(location.pose.position.ToUnityVector(), location.pose.orientation.ToUnityQuaternion());
return true;
}
return false;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Managered resource
}
// Non managered resource
//Debug.Log($"Space: DestroySpace({space})");
SpaceWrapper.Instance.DestroySpace(space);
space = 0;
disposed = true;
}
}
~Space()
{
Dispose(false);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8fbeadd31afbe14996c061ac261041d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,199 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Runtime.InteropServices;
using System;
using UnityEngine;
using AOT;
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace VIVE.OpenXR
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal class HookHandlerAttribute : Attribute
{
public string xrFuncName { get; }
/// <summary>
/// Set this function to handle the hook process in <see cref="ViveInterceptors.XrGetInstanceProcAddrInterceptor" />
/// </summary>
/// <param name="xrFuncName">The hooked openxr function name</param>
public HookHandlerAttribute(string xrFuncName)
{
this.xrFuncName = xrFuncName;
}
}
/// <summary>
/// This class is made for all features that need to intercept OpenXR API calls.
/// Some APIs will be called by Unity internally, and we need to intercept them in c# to get some information.
/// Append more interceptable functions for this class by adding a new partial class.
/// The partial class can help the delegate name be nice to read and search.
/// Please create per function in one partial class.
///
/// For all features want to use this class, please call <see cref="HookGetInstanceProcAddr" /> in your feature class.
/// For example:
/// protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
/// {
/// return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
/// }
/// </summary>
// For extending the ViveInterceptors class, create a new partial class and implement the required functions.
// For example:
// public partial class ViveInterceptors
// {
// [HookHandler("xrYourFunction")]
// private static XrResult OnHookXrYourFunction(XrInstance instance, string name, out IntPtr function)
// { ... }
// }
partial class ViveInterceptors
{
public const string TAG = "VIVE.OpenXR.ViveInterceptors";
static StringBuilder m_sb = null;
static StringBuilder sb {
get {
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
public static ViveInterceptors instance = null;
public static ViveInterceptors Instance
{
get
{
if (instance == null)
instance = new ViveInterceptors();
return instance;
}
}
public ViveInterceptors()
{
Log.D("ViveInterceptors");
RegisterFunctions();
}
delegate XrResult HookHandler(XrInstance instance, string name, out IntPtr function);
static readonly Dictionary<string, HookHandler> interceptors = new Dictionary<string, HookHandler>();
private static void RegisterFunctions()
{
var methods = typeof(ViveInterceptors).GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
foreach (var method in methods)
{
var attribute = method.GetCustomAttributes(typeof(HookHandlerAttribute), false).FirstOrDefault() as HookHandlerAttribute;
if (attribute != null)
{
Log.I(TAG, $"Registering hook handler {attribute.xrFuncName}");
interceptors.Add(attribute.xrFuncName, (HookHandler)method.CreateDelegate(typeof(HookHandler)));
}
}
}
private static readonly OpenXRHelper.xrGetInstanceProcAddrDelegate hookXrGetInstanceProcAddrHandle = new OpenXRHelper.xrGetInstanceProcAddrDelegate(XrGetInstanceProcAddrInterceptor);
private static readonly IntPtr hookGetInstanceProcAddrHandlePtr = Marshal.GetFunctionPointerForDelegate(hookXrGetInstanceProcAddrHandle);
static OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddrOriginal = null;
[MonoPInvokeCallback(typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate))]
private static XrResult XrGetInstanceProcAddrInterceptor(XrInstance instance, string name, out IntPtr function)
{
// Used to check if the original function is already hooked.
if (instance == 0 && name == "ViveInterceptorHooked")
{
function = IntPtr.Zero;
return XrResult.XR_SUCCESS;
}
// Check if the function is intercepted by other features
if (interceptors.ContainsKey(name))
{
// If no request for this function, call the original function directly.
if (!requiredFunctions.Contains(name))
return XrGetInstanceProcAddrOriginal(instance, name, out function);
var ret = interceptors[name](instance, name, out function);
if (ret == XrResult.XR_SUCCESS)
Log.I(TAG, name + " is intercepted");
return ret;
}
return XrGetInstanceProcAddrOriginal(instance, name, out function);
}
public IntPtr HookGetInstanceProcAddr(IntPtr func)
{
Log.D(TAG, "HookGetInstanceProcAddr");
if (XrGetInstanceProcAddrOriginal == null)
{
Log.D(TAG, "registering our own xrGetInstanceProcAddr");
XrGetInstanceProcAddrOriginal = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrGetInstanceProcAddrDelegate>(func);
#if UNITY_EDITOR
if (Application.isEditor) {
// This is a trick to check if the original function is already hooked by this class. Sometimes, the static XrGetInstanceProcAddrOriginal didn't work as expected.
Log.D(TAG, "Check if duplicate hooked by this script with instance=0 and \"ViveInterceptorHooked\" name. If following a loader error, ignore it.");
// E OpenXR-Loader: Error [SPEC | xrGetInstanceProcAddr | VUID-xrGetInstanceProcAddr-instance-parameter] : XR_NULL_HANDLE for instance but query for ViveInterceptorHooked requires a valid instance
// Call XrGetInstanceProcAddrOriginal to check if the original function is already hooked by this class
if (XrGetInstanceProcAddrOriginal(0, "ViveInterceptorHooked", out IntPtr function) == XrResult.XR_SUCCESS)
{
// If it is called successfully, it means the original function is already hooked. So we should return the original function.
Log.D(TAG, "Already hooked");
return func;
}
}
#endif
return hookGetInstanceProcAddrHandlePtr;
}
else
{
// Dont return hookGetInstanceProcAddrHandlePtr again.
// If this hook function is called by multiple features, it should only work at the first time.
// If called by other features, it should return the original function.
return func;
}
}
static readonly List<string> requiredFunctions = new List<string>();
/// <summary>
/// Call before <see cref="HookGetInstanceProcAddr" /> to add required functions."/>
/// </summary>
/// <param name="name"></param>
public void AddRequiredFunction(string name)
{
Log.D(TAG, $"AddRequiredFunction({name})");
if (!interceptors.ContainsKey(name))
{
Log.E(TAG, $"AddRequiredFunction({name}) failed. No such function.");
return;
}
if (!requiredFunctions.Contains(name))
requiredFunctions.Add(name);
// If your function support unregister, you can add the reference count here.
if (name == "xrLocateViews")
xrLocateViewsReferenceCount++;
}
/// <summary>
/// If no need to use this hooked function, call this will remove your requirement.
/// If all requirements are removed, the original function will be called directly.
/// </summary>
/// <param name="name"></param>
public void RemoveRequiredFunction(string name)
{
// If your function support unregister, you can add the reference count here.
if (requiredFunctions.Contains(name))
{
if (name == "xrLocateViews")
xrLocateViewsReferenceCount = Mathf.Max(xrLocateViewsReferenceCount--, 0);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 118a2474c266d174d834b364821865b5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,102 @@
// Copyright HTC Corporation All Rights Reserved.
#define DEBUG
using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine.Profiling;
using VIVE.OpenXR.FrameSynchronization;
namespace VIVE.OpenXR
{
partial class ViveInterceptors
{
[HookHandler("xrBeginSession")]
private static XrResult OnHookXrBeginSession(XrInstance instance, string name, out IntPtr function)
{
if (xrBeginSessionOrigin == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
xrBeginSessionOrigin = Marshal.GetDelegateForFunctionPointer<xrBeginSessionDelegate>(function);
}
function = xrBeginSessionPtr;
return XrResult.XR_SUCCESS;
}
#region xrBeginSession
public delegate XrResult xrBeginSessionDelegate(XrSession session, ref XrSessionBeginInfo beginInfo);
private static xrBeginSessionDelegate xrBeginSessionOrigin = null;
[MonoPInvokeCallback(typeof(xrBeginSessionDelegate))]
private static XrResult xrBeginSessionInterceptor(XrSession session, ref XrSessionBeginInfo beginInfo)
{
Profiler.BeginSample("VI:BeginSession");
XrResult result = XrResult.XR_ERROR_FUNCTION_UNSUPPORTED;
if (xrBeginSessionOrigin != null)
{
if (m_EnableFrameSynchronization)
{
frameSynchronizationSessionBeginInfo.mode = m_FrameSynchronizationMode;
frameSynchronizationSessionBeginInfo.next = beginInfo.next;
beginInfo.next = Marshal.AllocHGlobal(Marshal.SizeOf(frameSynchronizationSessionBeginInfo));
long offset = 0;
if (IntPtr.Size == 4)
offset = beginInfo.next.ToInt32();
else
offset = beginInfo.next.ToInt64();
IntPtr frame_synchronization_session_begin_info_ptr = new IntPtr(offset);
Marshal.StructureToPtr(frameSynchronizationSessionBeginInfo, frame_synchronization_session_begin_info_ptr, false);
#if DEBUG
if (IntPtr.Size == 4)
offset = beginInfo.next.ToInt32();
else
offset = beginInfo.next.ToInt64();
IntPtr fs_begin_info_ptr = new IntPtr(offset);
XrFrameSynchronizationSessionBeginInfoHTC fsBeginInfo = (XrFrameSynchronizationSessionBeginInfoHTC)Marshal.PtrToStructure(fs_begin_info_ptr, typeof(XrFrameSynchronizationSessionBeginInfoHTC));
sb.Clear().Append("xrBeginSessionInterceptor() beginInfo.next = (").Append(fsBeginInfo.type).Append(", ").Append(fsBeginInfo.mode).Append(")");
Log.D(sb);
#endif
}
result = xrBeginSessionOrigin(session, ref beginInfo);
}
else
{
Log.E("xrBeginSessionInterceptor() Not assign xrBeginSession!");
}
Profiler.EndSample();
return result;
}
private static readonly xrBeginSessionDelegate xrBeginSession = new xrBeginSessionDelegate(xrBeginSessionInterceptor);
private static readonly IntPtr xrBeginSessionPtr = Marshal.GetFunctionPointerForDelegate(xrBeginSession);
#endregion
private static XrFrameSynchronizationSessionBeginInfoHTC frameSynchronizationSessionBeginInfo = XrFrameSynchronizationSessionBeginInfoHTC.identity;
private static bool m_EnableFrameSynchronization = false;
private static XrFrameSynchronizationModeHTC m_FrameSynchronizationMode = XrFrameSynchronizationModeHTC.XR_FRAME_SYNCHRONIZATION_MODE_STABILIZED_HTC;
/// <summary>
/// Activate or deactivate the Frame Synchronization feature.
/// </summary>
/// <param name="active">True for activate</param>
/// <param name="mode">The <see cref="XrFrameSynchronizationModeHTC"/> used for Frame Synchronization.</param>
public void ActivateFrameSynchronization(bool active, XrFrameSynchronizationModeHTC mode)
{
m_EnableFrameSynchronization = active;
m_FrameSynchronizationMode = mode;
sb.Clear().Append("ActivateFrameSynchronization() ").Append(active ? "enable " : "disable ").Append(mode);
Log.D(sb);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c222b96d7eb4ca4bb6390e07b1967bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,98 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Runtime.InteropServices;
using System;
using AOT;
using UnityEngine.Profiling;
namespace VIVE.OpenXR
{
public partial class ViveInterceptors
{
[HookHandler("xrLocateViews")]
private static XrResult OnHookXrLocateViews(XrInstance instance, string name, out IntPtr function)
{
if (xrLocateViewsOriginal == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
xrLocateViewsOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrLocateViews>(function);
}
function = xrLocateViewsInterceptorPtr;
return XrResult.XR_SUCCESS;
}
public struct XrViewLocateInfo
{
public XrStructureType type;
public IntPtr next;
public XrViewConfigurationType viewConfigurationType;
public XrTime displayTime;
public XrSpace space;
}
public struct XrView
{
public XrStructureType type;
public IntPtr next;
public XrPosef pose;
public XrFovf fov;
}
public enum XrViewStateFlags {
ORIENTATION_VALID_BIT = 0x00000001,
POSITION_VALID_BIT = 0x00000002,
ORIENTATION_TRACKED_BIT = 0x00000004,
POSITION_TRACKED_BIT = 0x00000008,
}
public struct XrViewState
{
public XrStructureType type;
public IntPtr next;
public XrViewStateFlags viewStateFlags;
}
public delegate XrResult DelegateXrLocateViews(XrSession session, IntPtr /*XrViewLocateInfo*/ viewLocateInfo, IntPtr /*XrViewState*/ viewState, uint viewCapacityInput, ref uint viewCountOutput, IntPtr /*XrView*/ views);
private static readonly DelegateXrLocateViews xrLocateViewsInterceptorHandle = new DelegateXrLocateViews(XrLocateViewsInterceptor);
private static readonly IntPtr xrLocateViewsInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrLocateViewsInterceptorHandle);
static DelegateXrLocateViews xrLocateViewsOriginal = null;
static int xrLocateViewsReferenceCount = 0;
[MonoPInvokeCallback(typeof(DelegateXrLocateViews))]
private static XrResult XrLocateViewsInterceptor(XrSession session, IntPtr viewLocateInfo, IntPtr viewState, uint viewCapacityInput, ref uint viewCountOutput, IntPtr views)
{
// Call the original function if the reference count is less than or equal to 0
if (xrLocateViewsReferenceCount <= 0)
return xrLocateViewsOriginal(session, viewLocateInfo, viewState, viewCapacityInput, ref viewCountOutput, views);
Profiler.BeginSample("VI:LocateViewsA");
XrResult result = XrResult.XR_SUCCESS;
if (instance.BeforeOriginalLocateViews != null)
instance.BeforeOriginalLocateViews(session, viewLocateInfo, viewState, viewCapacityInput, ref viewCountOutput, views);
Profiler.EndSample();
result = xrLocateViewsOriginal(session, viewLocateInfo, viewState, viewCapacityInput, ref viewCountOutput, views);
Profiler.BeginSample("VI:LocateViewsB");
instance.AfterOriginalLocateViews?.Invoke(session, viewLocateInfo, viewState, viewCapacityInput, ref viewCountOutput, views);
Profiler.EndSample();
return result;
}
/// <summary>
/// If you return false, the original function will not be called.
/// </summary>
/// <returns></returns>
public delegate bool DelegateXrLocateViewsInterceptor(XrSession session, IntPtr viewLocateInfo, IntPtr viewState, uint viewCapacityInput, ref uint viewCountOutput, IntPtr views);
/// <summary>
/// Use this to intercept the original function. This will be called before the original function.
/// </summary>
public DelegateXrLocateViewsInterceptor BeforeOriginalLocateViews;
/// <summary>
/// Use this to intercept the original function. This will be called after the original function.
/// </summary>
public DelegateXrLocateViewsInterceptor AfterOriginalLocateViews;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5dfd24c69475c3740975bf5538de3869
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,143 @@
// Copyright HTC Corporation All Rights Reserved.
using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine.Profiling;
using VIVE.OpenXR.DisplayRefreshRate;
using VIVE.OpenXR.Passthrough;
using VIVE.OpenXR.UserPresence;
namespace VIVE.OpenXR
{
partial class ViveInterceptors
{
[HookHandler("xrPollEvent")]
private static XrResult OnHookXrPollEvent(XrInstance instance, string name, out IntPtr function)
{
if (xrPollEventOrigin == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
xrPollEventOrigin = Marshal.GetDelegateForFunctionPointer<xrPollEventDelegate>(function);
}
function = xrPollEventPtr;
return XrResult.XR_SUCCESS;
}
#region xrPollEvent
public delegate XrResult xrPollEventDelegate(XrInstance instance, ref XrEventDataBuffer eventData);
private static xrPollEventDelegate xrPollEventOrigin = null;
[MonoPInvokeCallback(typeof(xrPollEventDelegate))]
private static XrResult xrPollEventInterceptor(XrInstance instance, ref XrEventDataBuffer eventData)
{
Profiler.BeginSample("VI:PollEvent");
XrResult result = XrResult.XR_SUCCESS;
if (xrPollEventOrigin != null)
{
result = xrPollEventOrigin(instance, ref eventData);
if (result == XrResult.XR_SUCCESS)
{
sb.Clear().Append("xrPollEventInterceptor() xrPollEvent ").Append(eventData.type); Log.D("PollEvent", sb);
switch(eventData.type)
{
case XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_CHANGED_HTC:
if (XrEventDataPassthroughConfigurationImageRateChangedHTC.Get(eventData, out XrEventDataPassthroughConfigurationImageRateChangedHTC eventDataPassthroughConfigurationImageRate))
{
fromImageRate = eventDataPassthroughConfigurationImageRate.fromImageRate;
toImageRate = eventDataPassthroughConfigurationImageRate.toImageRate;
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_CHANGED_HTC")
.Append(", fromImageRate.srcImageRate: ").Append(fromImageRate.srcImageRate)
.Append(", fromImageRatesrc.dstImageRate: ").Append(fromImageRate.dstImageRate)
.Append(", toImageRate.srcImageRate: ").Append(toImageRate.srcImageRate)
.Append(", toImageRate.dstImageRate: ").Append(toImageRate.dstImageRate);
Log.D("PollEvent", sb.ToString());
VivePassthroughImageRateChanged.Send(fromImageRate.srcImageRate, fromImageRate.dstImageRate, toImageRate.srcImageRate, toImageRate.dstImageRate);
}
break;
case XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_QUALITY_CHANGED_HTC:
if (XrEventDataPassthroughConfigurationImageQualityChangedHTC.Get(eventData, out XrEventDataPassthroughConfigurationImageQualityChangedHTC eventDataPassthroughConfigurationImageQuality))
{
fromImageQuality = eventDataPassthroughConfigurationImageQuality.fromImageQuality;
toImageQuality = eventDataPassthroughConfigurationImageQuality.toImageQuality;
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_QUALITY_CHANGED_HTC")
.Append(", fromImageQuality: ").Append(fromImageQuality.scale)
.Append(", toImageQuality: ").Append(toImageQuality.scale);
Log.D("PollEvent", sb);
VivePassthroughImageQualityChanged.Send(fromImageQuality.scale, toImageQuality.scale);
}
break;
case XrStructureType.XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB:
if(XrEventDataDisplayRefreshRateChangedFB.Get(eventData, out XrEventDataDisplayRefreshRateChangedFB eventDataDisplayRefreshRate))
{
fromDisplayRefreshRate = eventDataDisplayRefreshRate.fromDisplayRefreshRate;
toDisplayRefreshRate = eventDataDisplayRefreshRate.toDisplayRefreshRate;
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB")
.Append(", fromDisplayRefreshRate: ").Append(fromDisplayRefreshRate)
.Append(", toDisplayRefreshRate: ").Append(toDisplayRefreshRate);
Log.D("PollEvent", sb);
ViveDisplayRefreshRateChanged.Send(fromDisplayRefreshRate, toDisplayRefreshRate);
}
break;
case XrStructureType.XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
if (XrEventDataSessionStateChanged.Get(eventData, out XrEventDataSessionStateChanged eventDataSession))
{
switch(eventDataSession.state)
{
case XrSessionState.XR_SESSION_STATE_READY:
isUserPresent = true;
break;
case XrSessionState.XR_SESSION_STATE_STOPPING:
isUserPresent = false;
break;
default:
break;
}
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED")
.Append(", session: ").Append(eventDataSession.session)
.Append(", state: ").Append(eventDataSession.state)
.Append(", isUserPresent: ").Append(isUserPresent);
Log.D("PollEvent", sb);
}
break;
case XrStructureType.XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT:
if (XrEventDataUserPresenceChangedEXT.Get(eventData, out XrEventDataUserPresenceChangedEXT eventDataUserPresence))
{
isUserPresent = eventDataUserPresence.isUserPresent;
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT")
.Append(", session: ").Append(eventDataUserPresence.session)
.Append(", isUserPresent: ").Append(isUserPresent);
Log.D("PollEvent", sb);
}
break;
default:
break;
}
}
//sb.Clear().Append("xrPollEventInterceptor() xrPollEvent result: ").Append(result).Append(", isUserPresent: ").Append(isUserPresent); Log.d("PollEvent", sb);
}
Profiler.EndSample();
return result;
}
private static readonly xrPollEventDelegate xrPollEvent = new xrPollEventDelegate(xrPollEventInterceptor);
private static readonly IntPtr xrPollEventPtr = Marshal.GetFunctionPointerForDelegate(xrPollEvent);
#endregion
private static bool isUserPresent = true;
public bool IsUserPresent() { return isUserPresent; }
private static float fromDisplayRefreshRate, toDisplayRefreshRate;
public float FromDisplayRefreshRate() { return fromDisplayRefreshRate; }
public float ToDisplayRefreshRate() { return toDisplayRefreshRate; }
private static XrPassthroughConfigurationImageRateHTC fromImageRate, toImageRate;
private static XrPassthroughConfigurationImageQualityHTC fromImageQuality, toImageQuality;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c2cc5716d3f563f49a47da6c1bd8ccbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,104 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Runtime.InteropServices;
using System;
using AOT;
using UnityEngine.Profiling;
namespace VIVE.OpenXR
{
partial class ViveInterceptors
{
[HookHandler("xrEndFrame")]
private static XrResult OnHookXrEndFrame(XrInstance instance, string name, out IntPtr function)
{
if (XrEndFrameOriginal == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
XrEndFrameOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrEndFrame>(function);
}
function = xrEndFrameInterceptorPtr;
return XrResult.XR_SUCCESS;
}
public struct XrCompositionLayerBaseHeader
{
public XrStructureType type; // This base structure itself has no associated XrStructureType value.
public System.IntPtr next;
public XrCompositionLayerFlags layerFlags;
public XrSpace space;
}
public struct XrFrameEndInfo
{
public XrStructureType type;
public System.IntPtr next;
public XrTime displayTime;
public XrEnvironmentBlendMode environmentBlendMode;
public uint layerCount;
public IntPtr layers; // XrCompositionLayerBaseHeader IntPtr array
}
public delegate XrResult DelegateXrEndFrame(XrSession session, ref XrFrameEndInfo frameEndInfo);
private static readonly DelegateXrEndFrame xrEndFrameInterceptorHandle = new DelegateXrEndFrame(XrEndFrameInterceptor);
private static readonly IntPtr xrEndFrameInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrEndFrameInterceptorHandle);
static DelegateXrEndFrame XrEndFrameOriginal = null;
[MonoPInvokeCallback(typeof(DelegateXrEndFrame))]
private static XrResult XrEndFrameInterceptor(XrSession session, ref XrFrameEndInfo frameEndInfo)
{
// instance must not null
//if (instance == null)
// return XrEndFrameOriginal(session, ref frameEndInfo);
Profiler.BeginSample("VI:EndFrameB");
XrResult result = XrResult.XR_SUCCESS;
bool ret = true;
if (instance.BeforeOriginalEndFrame != null)
ret = instance.BeforeOriginalEndFrame(session, ref frameEndInfo, ref result);
Profiler.EndSample();
if (!ret)
return result;
result = XrEndFrameOriginal(session, ref frameEndInfo);
Profiler.BeginSample("VI:EndFrameA");
instance.AfterOriginalEndFrame?.Invoke(session, ref frameEndInfo, ref result);
Profiler.EndSample();
return result;
}
/// <summary>
/// If you return false, the original function will not be called.
/// </summary>
/// <param name="session"></param>
/// <param name="frameEndInfo"></param>
/// <param name="result"></param>
/// <returns></returns>
public delegate bool DelegateXrEndFrameInterceptor(XrSession session, ref XrFrameEndInfo frameEndInfo, ref XrResult result);
/// <summary>
/// Use this to intercept the original function. This will be called before the original function.
/// </summary>
public DelegateXrEndFrameInterceptor BeforeOriginalEndFrame;
/// <summary>
/// Use this to intercept the original function. This will be called after the original function.
/// </summary>
public DelegateXrEndFrameInterceptor AfterOriginalEndFrame;
#if PERFORMANCE_TEST
public delegate XrResult DelegateXrLocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location);
private static readonly DelegateXrLocateSpace xrLocateSpaceInterceptorHandle = new DelegateXrLocateSpace(XrLocateSpaceInterceptor);
private static readonly IntPtr xrLocateSpaceInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrLocateSpaceInterceptorHandle);
static DelegateXrLocateSpace XrLocateSpaceOriginal = null;
[MonoPInvokeCallback(typeof(DelegateXrLocateSpace))]
public static XrResult XrLocateSpaceInterceptor(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location)
{
Profiler.BeginSample("VI:LocateSpace");
var ret = XrLocateSpaceOriginal(space, baseSpace, time, ref location);
Profiler.EndSample();
return ret;
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6bf7cf55d82ac6343b4eda92d1197a66
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,91 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Runtime.InteropServices;
using System;
using AOT;
using UnityEngine.Profiling;
namespace VIVE.OpenXR
{
public partial class ViveInterceptors
{
[HookHandler("xrGetVisibilityMaskKHR")]
private static XrResult OnHookXrGetVisibilityMaskKHR(XrInstance instance, string name, out IntPtr function)
{
if (xrGetVisibilityMaskKHROriginal == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
xrGetVisibilityMaskKHROriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrGetVisibilityMaskKHR>(function);
}
function = xrGetVisibilityMaskKHRInterceptorPtr;
return XrResult.XR_SUCCESS;
}
public enum XrVisibilityMaskTypeKHR
{
HIDDEN_TRIANGLE_MESH_KHR = 1,
VISIBLE_TRIANGLE_MESH_KHR = 2,
LINE_LOOP_KHR = 3,
}
public struct XrVisibilityMaskKHR
{
public XrStructureType type;
public IntPtr next;
public uint vertexCapacityInput;
public uint vertexCountOutput;
public IntPtr vertices; // XrVector2f array
public uint indexCapacityInput;
public uint indexCountOutput;
public IntPtr indices; // uint array
}
// XrCompositionLayerSpaceWarpInfoFlagsFB bits
public delegate XrResult DelegateXrGetVisibilityMaskKHR(XrSession session, XrViewConfigurationType viewConfigurationType, uint viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, ref XrVisibilityMaskKHR visibilityMask);
private static readonly DelegateXrGetVisibilityMaskKHR xrGetVisibilityMaskKHRInterceptorHandle = new DelegateXrGetVisibilityMaskKHR(XrGetVisibilityMaskKHRInterceptor);
private static readonly IntPtr xrGetVisibilityMaskKHRInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrGetVisibilityMaskKHRInterceptorHandle);
static DelegateXrGetVisibilityMaskKHR xrGetVisibilityMaskKHROriginal = null;
[MonoPInvokeCallback(typeof(DelegateXrGetVisibilityMaskKHR))]
private static XrResult XrGetVisibilityMaskKHRInterceptor(XrSession session, XrViewConfigurationType viewConfigurationType, uint viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, ref XrVisibilityMaskKHR visibilityMask)
{
// instance must not null
//if (instance == null)
// return XrGetVisibilityMaskKHROriginal(session, ref frameEndInfo);
Profiler.BeginSample("VI:GetVMB");
XrResult result = XrResult.XR_SUCCESS;
bool ret = true;
if (instance.BeforeOriginalGetVisibilityMaskKHR != null)
ret = instance.BeforeOriginalGetVisibilityMaskKHR(session, viewConfigurationType, viewIndex, visibilityMaskType, ref visibilityMask, ref result);
Profiler.EndSample();
if (!ret)
return result;
result = xrGetVisibilityMaskKHROriginal(session, viewConfigurationType, viewIndex, visibilityMaskType, ref visibilityMask);
Profiler.BeginSample("VI:GetVMA");
instance.AfterOriginalGetVisibilityMaskKHR?.Invoke(session, viewConfigurationType, viewIndex, visibilityMaskType, ref visibilityMask, ref result);
Profiler.EndSample();
return result;
}
/// <summary>
/// If you return false, the original function will not be called.
/// </summary>
/// <param name="session"></param>
/// <param name="frameEndInfo"></param>
/// <param name="result"></param>
/// <returns></returns>
public delegate bool DelegateXrGetVisibilityMaskKHRInterceptor(XrSession session, XrViewConfigurationType viewConfigurationType, uint viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, ref XrVisibilityMaskKHR visibilityMask, ref XrResult result);
/// <summary>
/// Use this to intercept the original function. This will be called before the original function.
/// </summary>
public DelegateXrGetVisibilityMaskKHRInterceptor BeforeOriginalGetVisibilityMaskKHR;
/// <summary>
/// Use this to intercept the original function. This will be called after the original function.
/// </summary>
public DelegateXrGetVisibilityMaskKHRInterceptor AfterOriginalGetVisibilityMaskKHR;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a4c00b0b7df78d34d89cd728c9de0672
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,120 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Runtime.InteropServices;
using System;
using UnityEngine;
using AOT;
using UnityEngine.Profiling;
namespace VIVE.OpenXR
{
partial class ViveInterceptors
{
[HookHandler("xrWaitFrame")]
private static XrResult OnHookXrWaitFrame(XrInstance instance, string name, out IntPtr function)
{
if (XrWaitFrameOriginal == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
XrWaitFrameOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrWaitFrame>(function);
}
function = xrWaitFrameInterceptorPtr;
return XrResult.XR_SUCCESS;
}
public struct XrFrameWaitInfo
{
public XrStructureType type;
public IntPtr next;
}
public struct XrFrameState
{
public XrStructureType type;
public IntPtr next;
public XrTime predictedDisplayTime;
public XrDuration predictedDisplayPeriod;
public XrBool32 shouldRender;
}
bool isWaitFrameIntercepted = false;
public delegate XrResult DelegateXrWaitFrame(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState);
private static readonly DelegateXrWaitFrame xrWaitFrameInterceptorHandle = new DelegateXrWaitFrame(XrWaitFrameInterceptor);
private static readonly IntPtr xrWaitFrameInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrWaitFrameInterceptorHandle);
static DelegateXrWaitFrame XrWaitFrameOriginal = null;
[MonoPInvokeCallback(typeof(DelegateXrWaitFrame))]
private static XrResult XrWaitFrameInterceptor(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState)
{
// instance must not null
//if (instance == null)
// return XrWaitFrameOriginal(session, ref frameWaitInfo, ref frameState);
Profiler.BeginSample("VI:WaitFrame");
instance.isWaitFrameIntercepted = true;
XrResult result = XrResult.XR_SUCCESS;
if (instance.BeforeOriginalWaitFrame != null &&
!instance.BeforeOriginalWaitFrame(session, ref frameWaitInfo, ref frameState, ref result))
{
Profiler.EndSample();
return result;
}
var ret = XrWaitFrameOriginal(session, ref frameWaitInfo, ref frameState);
instance.AfterOriginalWaitFrame?.Invoke(session, ref frameWaitInfo, ref frameState, ref result);
currentFrameState = frameState;
Profiler.EndSample();
return result;
}
static XrFrameState currentFrameState = new XrFrameState() { predictedDisplayTime = 0 };
/// <summary>
/// Get the waitframe's result: XrFrameState. This result used in update is not matching the current frame. Use it after onBeforeRender.
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public XrFrameState GetCurrentFrameState()
{
if (!isWaitFrameIntercepted) throw new Exception("ViveInterceptors is not intercepted");
return currentFrameState;
}
/// <summary>
/// Must request xrWaitFrame before calling this function. This result used in update is not matching the current frame. Use it after onBeforeRender.
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public XrTime GetPredictTime()
{
if (!isWaitFrameIntercepted) throw new Exception("ViveInterceptors is not intercepted");
//Debug.Log($"{TAG}: XrWaitFrameInterceptor(predictedDisplayTime={currentFrameState.predictedDisplayTime}");
if (currentFrameState.predictedDisplayTime == 0)
return new XrTime((long)(1000000L * (Time.unscaledTimeAsDouble + 0.011f)));
else
return currentFrameState.predictedDisplayTime;
}
/// <summary>
/// Register WaitFrame event
/// </summary>
/// <param name="session"></param>
/// <param name="frameWaitInfo"></param>
/// <param name="frameState"></param>
/// <param name="result"></param>
/// <returns></returns>
public delegate bool DelegateXrWaitFrameInterceptor(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState, ref XrResult result);
/// <summary>
/// Use this to intercept the original function. This will be called before the original function.
/// </summary>
public DelegateXrWaitFrameInterceptor BeforeOriginalWaitFrame;
/// <summary>
/// Use this to intercept the original function. This will be called after the original function.
/// </summary>
public DelegateXrWaitFrameInterceptor AfterOriginalWaitFrame;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9bfc07f267ee39b47a4bcbb8c1c786cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,247 @@
// Copyright HTC Corporation All Rights Reserved.
#if UNITY_ANDROID && !UNITY_EDITOR
using System.Runtime.InteropServices;
#endif
using System.Text;
// Non android will need UnityEngine
using UnityEngine;
namespace VIVE.OpenXR
{
public static class Log
{
public const string TAG = "VIVE.OpenXR";
#if UNITY_ANDROID && !UNITY_EDITOR
[DllImport("liblog.so")]
private static extern int __android_log_print(int prio, string tag, string fmt, string msg);
#endif
// Use ("%s", message) instead of just (message) is because of the following reason:
// In case message contains special characters like %, \n, \r, etc. It will be treated as format string.
// This is a little waste of performance, but it's safer.
/// <summary>
/// Not show in Standalone
/// </summary>
public static void D(string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, TAG, "%s", message); // Android Debug
#endif
}
public static void I(string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, TAG, "%s", message); // Android Info
#else
Debug.Log(message);
#endif
}
public static void W(string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, TAG, "%s", message); // Android Warning
#else
Debug.LogWarning(message);
#endif
}
public static void E(string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, TAG, "%s", message); // Android Error
#else
Debug.LogError(message);
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void D(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, tag, "%s", message);
#endif
}
public static void I(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, tag, "%s", message);
#else
Debug.LogFormat("{0}: {1}", tag, message);
#endif
}
public static void W(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, tag, "%s", message); // Android Warning
#else
Debug.LogWarningFormat("{0}: {1}", tag, message);
#endif
}
public static void E(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, tag, "%s", message); // Android Error
#else
Debug.LogErrorFormat("{0}: {1}", tag, message);
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void D(StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, TAG, "%s", message.ToString());
#endif
}
public static void I(StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, TAG, "%s", message.ToString());
#else
Debug.Log(message.ToString());
#endif
}
public static void W(StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, TAG, "%s", message.ToString()); // Android Warning
#else
Debug.LogWarning(message.ToString());
#endif
}
public static void E(StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, TAG, "%s", message.ToString()); // Android Error
#else
Debug.LogError(message.ToString());
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void D(string tag, StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, tag, "%s", message.ToString());
#endif
}
public static void I(string tag, StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, tag, "%s", message.ToString());
#else
Debug.LogFormat("{0}: {1}", tag, message.ToString());
#endif
}
public static void W(string tag, StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, tag, "%s", message.ToString()); // Android Warning
#else
Debug.LogWarningFormat("{0}: {1}", tag, message.ToString());
#endif
}
public static void E(string tag, StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, tag, "%s", message.ToString()); // Android Error
#else
Debug.LogErrorFormat("{0}: {1}", tag, message.ToString());
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void DFmt(string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, TAG, "%s", string.Format(fmt, args));
#endif
}
public static void IFmt(string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, TAG, "%s", string.Format(fmt, args));
#else
Debug.LogFormat(fmt, args);
#endif
}
public static void WFmt(string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, TAG, "%s", string.Format(fmt, args)); // Android Warning
#else
Debug.LogWarningFormat(fmt, args);
#endif
}
public static void EFmt(string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, TAG, "%s", string.Format(fmt, args)); // Android Error
#else
Debug.LogErrorFormat(fmt, args);
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void DFmt(string tag, string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, tag, "%s", string.Format(fmt, args));
#endif
}
public static void IFmt(string tag, string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, tag, "%s", string.Format(fmt, args));
#else
Debug.LogFormat("{0}: {1}", tag, string.Format(fmt, args));
#endif
}
public static void WFmt(string tag, string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, tag, "%s", string.Format(fmt, args)); // Android Warning
#else
Debug.LogWarningFormat("{0}: {1}", tag, fmt, string.Format(fmt, args));
#endif
}
public static void EFmt(string tag, string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, tag, "%s", string.Format(fmt, args)); // Android Error
#else
Debug.LogErrorFormat("{0}: {1}", tag, fmt, string.Format(fmt, args));
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9750d8d4e8eb4994088534cb111510d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,338 @@
using AOT;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace VIVE.OpenXR.Common.RenderThread
{
#region syncObject
public class Message
{
public bool isFree = true;
}
/// <summary>
/// MessagePool class manages a pool of message objects for reuse. You can enter any kind of message object.
/// However when obtain, the message object will not able to cast to the type you want.
/// You should only use one kind of message. Not mix different kind of message.
/// </summary>
public class MessagePool
{
// pool member is used to store message objects in a list.
// Note that the size of this list will dynamically adjust as needed but will not automatically shrink.
private readonly List<Message> pool = new List<Message>(2) { };
private int index = 0;
public MessagePool() { }
// Next method calculates the next index value for cycling through message objects in the pool.
private int Next(int value)
{
if (++value >= pool.Count)
value = 0;
return value;
}
// Obtain method retrieves a message object from the pool.
// Ensure proper state setup for the message after retrieval and call Release() to the message after use.
public T Obtain<T>() where T : Message, new()
{
int c = pool.Count;
int i = index;
for (int j = 0; j < c; i++, j++)
{
if (i >= c)
i = 0;
if (pool[i].isFree)
{
//Debug.LogError("Obtain idx=" + i);
index = i;
return (T)pool[i];
}
}
index = Next(i);
var newItem = new T()
{
isFree = true
};
pool.Insert(index, newItem);
//Log.d("RT.MessagePool.Obtain<" + typeof(T) + ">() pool count=" + pool.Count); // Not to expose developer's type.
Log.D("RT.MessagePool.Obtain() pool count=" + pool.Count);
return newItem;
}
// Lock method marks a message as "in use" to prevent other code from reusing it.
// This is already called to the message obtained from the pool.
public static void Lock(Message msg)
{
msg.isFree = false;
}
/// <summary>
/// Release method marks a message as "free" so that other code can reuse it.
/// You can use it in RenderThread. It will not trigger the GC event.
/// </summary>
/// <param name="msg"></param>
public static void Release(Message msg)
{
msg.isFree = true;
}
}
/// <summary>
/// PreAllocatedQueue class is a message queue based on MessagePool for preallocating message objects.
/// Its main functionality is to add message objects to the queue and retrieve them from the queue.
/// Messages should be enqueued in GameThread and dequeued in RenderThread.
/// In render thread, dequeue will not trigger the GC event. Because the queue is preallocated.
/// The 'lock' expression is not used for list's size change. Because lock should be avoid used in RenderThread.
/// Set the queueSize as the double count of message you want to pass to render thread in one frame, and the
/// list will never change size during runtime. Therefore we don't need to use 'lock' to protect the list.
/// </summary>
public class PreAllocatedQueue : MessagePool
{
// list member is used to store preallocated message objects in a list.
// Note that the size of this list is set during initialization and does not dynamically adjust.
private List<Message> list = new List<Message>();
private int queueBegin = 0;
private int queueEnd = 0;
/// <summary>
/// The queueSize should be the double count of message you want to pass to render thread in one frame.
/// </summary>
/// <param name="queueSize"></param>
public PreAllocatedQueue(int queueSize = 2) : base()
{
for (int i = 0; i < queueSize; i++)
{
list.Add(null);
}
}
private int Next(int value)
{
if (++value >= list.Count)
value = 0;
return value;
}
/// <summary>
/// Enqueue method adds a message object to the queue.
/// If the queue is full, the new message is added to the end of the list.
///
/// This function is designed to use the message object obtained from the MessagePool.
/// Ensure only one type of message object is used in the queue.
///
/// Enqueue will increase the queue size if the queue is full. This may trigger GC.Alloc.
/// This function should be used in GameThread.
/// </summary>
/// <param name="msg"></param>
public void Enqueue(Message msg)
{
Lock(msg);
queueEnd = Next(queueEnd);
// If the queue is full, add the message to the end of the list. Should not let it happen.
// Use larger queue size to avoid this issue.
// If you see the error log here, you should increase the queue size in your design.
if (queueEnd == queueBegin)
{
// Should let Insert and queueBegin be atomic. No lock protection here.
list.Insert(queueEnd, msg);
queueBegin++;
Debug.LogError("RT.MessagePool.Enqueue() list count=" + list.Count);
}
else
{
list[queueEnd] = msg;
}
}
/// <summary>
/// Dequeue method retrieves a message object from the queue.
/// This method returns the first message object in the queue and removes it from the queue.
/// This function will not trigger the GC event. Free to use in RenderThread.
/// After use the Message, call Release() to the message.
/// </summary>
/// <returns></returns>
public Message Dequeue()
{
// No lock protection here. If list is not change size, it is safe.
// However if list changed size, it is safe in most case.
queueBegin = Next(queueBegin);
return list[queueBegin];
}
}
/// <summary>
/// RenderThreadTask class is used to execute specified tasks on the rendering thread.
/// You don't need to develop a native function to run your task on the rendering thread.
/// And you don't need to design how to pass data to render thread.
/// This class can be run in Unity Editor since Unity 2021. Test your code in Unity Editor can save your time.
///
/// You should only create RenderThreadTask as static readonly. Do not create RenderThreadTask in dynamic.
///
/// You should not run Unity.Engine code in RenderThread. It will cause the Unity.Engine to hang.
/// Any exception will not be caught and shown in RenderThread.
/// You should print your error message out to clearify your issue.
///
/// The 'lock' expression is not used here. Because I believe the lock is not necessary in this case.
/// And the lock will cause the performance issue. All the design here help you not to use 'lock'.
/// </summary>
public class RenderThreadTask
{
private static IntPtr GetFunctionPointerForDelegate(Delegate del)
{
return System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(del);
}
public delegate void RenderEventDelegate(int e);
private static readonly RenderEventDelegate handle = new RenderEventDelegate(RunSyncObjectInRenderThread);
private static readonly IntPtr handlePtr = GetFunctionPointerForDelegate(handle);
public delegate void Receiver(PreAllocatedQueue dataQueue);
// CommandList is used to store all RenderThreadTask objects.
// Do not create RenderThreadTask object in dynamic. It will cause the CommandList to increase infinitly.
private static List<RenderThreadTask> CommandList = new List<RenderThreadTask>();
private PreAllocatedQueue queue;
public PreAllocatedQueue Queue { get { return queue; } }
private readonly Receiver receiver;
private readonly int id;
/// <summary>
/// Input the receiver as render thread callback. The receiver will be executed in render thread.
/// queueSize should be the double count of message you want to pass to render thread in one frame.
/// </summary>
/// <param name="render">The callback in render thread.</param>
/// <param name="queueSize">If issue this event once in a frame, set queueSize as 2.</param>
/// <exception cref="ArgumentNullException"></exception>
public RenderThreadTask(Receiver render, int queueSize = 2)
{
queue = new PreAllocatedQueue(queueSize);
receiver = render;
if (receiver == null)
throw new ArgumentNullException("receiver should not be null");
CommandList.Add(this);
id = CommandList.IndexOf(this);
}
~RenderThreadTask()
{
// Remove could be in a random order, and will cause orderId change. DO not remove any of them.
//try { CommandList.Remove(this); } finally { }
}
void IssuePluginEvent(IntPtr callback, int eventID)
{
// Older version will hang after run script in render thread.
GL.IssuePluginEvent(callback, eventID);
return;
}
void IssuePluginEvent(CommandBuffer cmdBuf, IntPtr callback, int eventID)
{
cmdBuf.IssuePluginEvent(callback, eventID);
return;
}
/// <summary>
/// IssueEvent method submits this task's receiver, which is set in constructor, to be executed on the rendering thread.
/// </summary>
public void IssueEvent()
{
// Let the render thread run the RunSyncObjectInRenderThread(id)
IssuePluginEvent(handlePtr, id);
}
public void IssueInCommandBuffer(CommandBuffer cmdBuf)
{
// Let the render thread run the RunSyncObjectInRenderThread(id)
IssuePluginEvent(cmdBuf, handlePtr, id);
}
// Called by RunSyncObjectInRenderThread()
private void Receive()
{
receiver(queue);
}
// RunSyncObjectInRenderThread method is a static method used to execute a specified task on the rendering thread.
// This method is invoked by Unity's rendering event mechanism and does not need to be called directly by developers.
[MonoPInvokeCallback(typeof(RenderEventDelegate))]
private static void RunSyncObjectInRenderThread(int id)
{
CommandList[id].Receive();
}
}
#endregion
#region sample
// Not to compile this sample into your application. Just for reference. You can run this sample in Unity Editor and it will work.
#if UNITY_EDITOR
public class ViveRenderThreadTaskSample : MonoBehaviour
{
// Create your own message class.
internal class SampleMessage : Message
{
public int dataPassedToRenderThread;
}
// Use static readonly to create RenderThreadTask. Keep internal to avoid miss use by other developers.
internal static readonly RenderThreadTask sampleRenderThreadTask1 = new RenderThreadTask(SampleReceiver1);
// Different task use different RenderThreadTask and different recevier.
internal static readonly RenderThreadTask sampleRenderThreadTask2 = new RenderThreadTask(SampleReceiver2);
private static void SampleReceiver1(PreAllocatedQueue dataQueue)
{
var msg = dataQueue.Dequeue() as SampleMessage;
if (msg != null)
{
// Keep data before release. Use local variable to keep data and release msg early. Should not keep the msg instance itself.
var data = msg.dataPassedToRenderThread;
// Make sure release the msg if finished. Other wise the memory will keep increasing when Obtain.
MessagePool.Release(msg);
Debug.Log("Task1, the data passed to render thread: " + data);
}
}
private static void SampleReceiver2(PreAllocatedQueue dataQueue)
{
var msg = dataQueue.Dequeue() as SampleMessage;
if (msg != null)
{
// Keep data before release. Use local variable to keep data and release msg early. Should not keep the msg instance itself.
var data = msg.dataPassedToRenderThread;
// Make sure release the msg if finished. Other wise the memory will keep increasing when Obtain.
MessagePool.Release(msg);
Debug.Log("Task2, the data passed to render thread: " + data);
}
}
// Send a message to the render thread every frame.
private void Update()
{
// Make sure only one kind of message object is used in the queue.
var msg = sampleRenderThreadTask1.Queue.Obtain<SampleMessage>();
msg.dataPassedToRenderThread = 123;
sampleRenderThreadTask1.Queue.Enqueue(msg);
sampleRenderThreadTask1.IssueEvent();
}
// Send a message to render thread when something clicked. Make sure only one click in one frame because the queue size is only two.
public void OnClicked()
{
// Reuse the same message type is ok.
var msg = sampleRenderThreadTask2.Queue.Obtain<SampleMessage>();
msg.dataPassedToRenderThread = 234;
sampleRenderThreadTask2.Queue.Enqueue(msg);
sampleRenderThreadTask2.IssueEvent();
}
}
#endif
#endregion
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 251b4bedf6420fc4e84be778e501343f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 978c35d873ccc3d449ae82aea475ac75
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1e3437a00c6e7214d91d99576ac4c563
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ae071276547dc9f4f92d41c0c48666dc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,129 @@
// "VIVE SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the VIVE SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
Shader "VIVE/OpenXR/CompositionLayerUICanvas/MultiLayerCanvasUI"
{
Properties
{
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
_Color("Tint", Color) = (1,1,1,1)
_SrcColBlendMode("Source Color Blend Mode", Float) = 5
_SrcAlpBlendMode("Source Alpha Blend Mode", Float) = 5
_DstColBlendMode("Destination Color Blend Mode", Float) = 10
_DstAlpBlendMode("Destination Alpha Blend Mode", Float) = 10
_StencilComp("Stencil Comparison", Float) = 8
_Stencil("Stencil ID", Float) = 0
_StencilOp("Stencil Operation", Float) = 0
_StencilWriteMask("Stencil Write Mask", Float) = 255
_StencilReadMask("Stencil Read Mask", Float) = 255
_ColorMask("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
Stencil
{
Ref[_Stencil]
Comp[_StencilComp]
Pass[_StencilOp]
ReadMask[_StencilReadMask]
WriteMask[_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest[unity_GUIZTestMode]
Blend [_SrcColBlendMode] [_DstColBlendMode], [_SrcAlpBlendMode] [_DstAlpBlendMode]
ColorMask[_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float4 _MainTex_ST;
v2f vert(appdata_t v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.worldPosition = v.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
OUT.color = v.color * _Color;
return OUT;
}
fixed4 frag(v2f IN) : SV_Target
{
half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
#ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
#endif
#ifdef UNITY_UI_ALPHACLIP
clip(color.a - 0.001);
#endif
return color;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: be5f9629105213e4b9b6269706555141
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,89 @@
// "VIVE SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the VIVE SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
Shader "VIVE/OpenXR/CompositionLayer/Texture2DBlitShader"
{
Properties
{
_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
}
SubShader
{
Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
ZWrite Off
ColorMask RGBA
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_local _ LINEAR_TO_SRGB_COLOR
#pragma multi_compile_local _ LINEAR_TO_SRGB_ALPHA
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
int _LinearColorSpaceConversionNeeded;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
#if UNITY_UV_STARTS_AT_TOP
o.uv.y = 1.0 - o.uv.y;
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
//Linear to sRGB approximation
#ifdef LINEAR_TO_SRGB_COLOR
float3 S1c = sqrt(col.rgb);
float3 S2c = sqrt(S1c);
float3 S3c = sqrt(S2c);
col.rgb = 0.662002687 * S1c + 0.684122060 * S2c - 0.323583601 * S3c - 0.0225411470 * col.rgb;
#endif
#ifdef LINEAR_TO_SRGB_ALPHA
float S1a = sqrt(col.a);
float S2a = sqrt(S1a);
float S3a = sqrt(S2a);
col.a = 0.662002687 * S1a + 0.684122060 * S2a - 0.323583601 * S3a - 0.0225411470 * col.a;
#endif
return col;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 94841cafb08321549b83677281ddc49b
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,80 @@
// "VIVE SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the VIVE SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
Shader "VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_ColorScale("Color Scale", Color) = (1,1,1,1)
_ColorBias("Color Bias", Color) = (0,0,0,0)
}
SubShader
{
Tags {"Queue" = "Transparent" "RenderType" = "Transparent"}
LOD 500
ZWrite On
Blend Zero OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_local _ COLOR_SCALE_BIAS_ENABLED
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _ColorScale;
fixed4 _ColorBias;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
#ifdef COLOR_SCALE_BIAS_ENABLED
col *= _ColorScale.a;
col += _ColorBias.a;
#endif
return col;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 33fd51a7cf157a44683ddd081be6c46c
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,73 @@
// "VIVE SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the VIVE SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
Shader "VIVE/OpenXR/CompositionLayer/UnderlayAlphaZeroSolid"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_ColorScale("Color Scale", Color) = (1,1,1,1)
_ColorBias("Color Bias", Color) = (0,0,0,0)
}
SubShader
{
Tags {"Queue" = "Transparent" "RenderType" = "Transparent"}
LOD 500
ZWrite On
Blend Zero Zero
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_local _ COLOR_SCALE_BIAS_ENABLED
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _ColorScale;
fixed4 _ColorBias;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 33a39a24d09f3cf48ae13735ee2209f6
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 140ef7005df5ced47a85fa81ac8a8a20
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 221c4e845ba39fa4396461ac8c3b9e8e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,339 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using VIVE.OpenXR.CompositionLayer;
namespace VIVE.OpenXR.CompositionLayer
{
public class CompositionLayerManager : MonoBehaviour
{
private uint maxLayerCount = 0;
private static CompositionLayerManager instance = null;
private List<CompositionLayer> compositionLayers = new List<CompositionLayer>();
private List<CompositionLayer> compositionLayersToBeSubscribed = new List<CompositionLayer>();
private List<CompositionLayer> compositionLayersToBeUnsubscribed = new List<CompositionLayer>();
private bool isOnBeforeRenderSubscribed = false;
private ViveCompositionLayer compositionLayerFeature = null;
private const string LOG_TAG = "VIVE_CompositionLayerManager";
static void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
static void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); }
static void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
#region public parameter access functions
public static CompositionLayerManager GetInstance()
{
if (instance == null)
{
GameObject CompositionLayerManagerGO = new GameObject("MultiLayerManager", typeof(CompositionLayerManager));
instance = CompositionLayerManagerGO.GetComponent<CompositionLayerManager>();
}
return instance;
}
public static bool CompositionLayerManagerExists()
{
return (instance != null);
}
public int MaxLayerCount()
{
return (int)maxLayerCount;
}
public int RemainingLayerCount()
{
int count = (int)maxLayerCount - compositionLayers.Count;
if (count < 0)
{
return 0;
}
return count;
}
public int CurrentLayerCount()
{
return compositionLayers.Count;
}
#endregion
#region Monobehaviour Lifecycle
void Awake()
{
if (instance == null)
{
instance = this;
}
else if (instance != this)
{
Destroy(instance);
instance = this;
}
}
private void Start()
{
UpdateMaxLayerCount();
}
public delegate void CompositionLayerManagerOnBeforeRender();
public event CompositionLayerManagerOnBeforeRender CompositionLayerManagerOnBeforeRenderDelegate = null;
private void OnBeforeRender()
{
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
if (compositionLayerFeature != null)
{
if (compositionLayerFeature.XrSessionEnding)
{
DEBUG("XrSession is ending, stop all layers");
foreach (CompositionLayer layer in compositionLayers) //All active layers
{
if (!compositionLayersToBeUnsubscribed.Contains(layer) && !compositionLayersToBeSubscribed.Contains(layer))
{
//Add currently active layers that are not scheduled for termination to the "To be subscribed" list
compositionLayersToBeSubscribed.Add(layer);
}
layer.TerminateLayer();
}
compositionLayers.Clear();
foreach (CompositionLayer layer in compositionLayersToBeUnsubscribed) //All layers to be terminated
{
layer.TerminateLayer();
}
compositionLayersToBeUnsubscribed.Clear();
return;
}
}
else
{
ERROR("compositionLayerFeature not found");
}
bool CompositionLayerStatusUpdateNeeded = false;
//Process Sub and Unsub list in bulk at once per frame
if (compositionLayersToBeUnsubscribed.Count > 0)
{
foreach (CompositionLayer layerToBeRemoved in compositionLayersToBeUnsubscribed)
{
DEBUG("CompositionLayersToBeUnsubscribed: Processing");
if (compositionLayers.Contains(layerToBeRemoved) && !compositionLayersToBeSubscribed.Contains(layerToBeRemoved))
{
layerToBeRemoved.TerminateLayer();
compositionLayers.Remove(layerToBeRemoved);
}
}
compositionLayersToBeUnsubscribed.Clear();
CompositionLayerStatusUpdateNeeded = true;
}
if (compositionLayersToBeSubscribed.Count > 0)
{
DEBUG("CompositionLayersToBeSubscribed: Processing");
foreach (CompositionLayer layerToBeAdded in compositionLayersToBeSubscribed)
{
if (!compositionLayers.Contains(layerToBeAdded))
{
compositionLayers.Add(layerToBeAdded);
DEBUG("Add new layer");
}
else if (layerToBeAdded.isRenderPriorityChanged)
{
DEBUG("Layer RenderPriority changed");
}
}
compositionLayersToBeSubscribed.Clear();
CompositionLayerStatusUpdateNeeded = true;
}
if (CompositionLayerStatusUpdateNeeded)
{
DEBUG("CompositionLayerStatusUpdateNeeded");
UpdateLayerStatus();
CompositionLayerStatusUpdateNeeded = false;
}
CompositionLayerManagerOnBeforeRenderDelegate?.Invoke();
}
private void OnEnable()
{
if (!isOnBeforeRenderSubscribed)
{
Application.onBeforeRender += OnBeforeRender;
isOnBeforeRenderSubscribed = true;
}
}
private void OnDisable()
{
if (isOnBeforeRenderSubscribed)
{
Application.onBeforeRender -= OnBeforeRender;
isOnBeforeRenderSubscribed = false;
}
}
private void OnDestroy()
{
if (isOnBeforeRenderSubscribed)
{
Application.onBeforeRender -= OnBeforeRender;
isOnBeforeRenderSubscribed = false;
}
}
#endregion
public void SubscribeToLayerManager(CompositionLayer layerToBeAdded)
{
if (compositionLayersToBeSubscribed == null)
{
DEBUG("SubscribeToLayerManager: Layer List not found. Creating a new one.");
compositionLayersToBeSubscribed = new List<CompositionLayer>();
}
if (!compositionLayersToBeSubscribed.Contains(layerToBeAdded))
{
DEBUG("SubscribeToLayerManager: Add layer");
compositionLayersToBeSubscribed.Add(layerToBeAdded);
}
}
public void UnsubscribeFromLayerManager(CompositionLayer layerToBeRemoved, bool isImmediate)
{
if (compositionLayersToBeUnsubscribed == null)
{
DEBUG("UnsubscribeFromLayerManager: Layer List not found. Creating a new one.");
compositionLayersToBeUnsubscribed = new List<CompositionLayer>();
}
if (!compositionLayersToBeUnsubscribed.Contains(layerToBeRemoved) && !isImmediate)
{
DEBUG("UnsubscribeFromLayerManager: Remove layer");
compositionLayersToBeUnsubscribed.Add(layerToBeRemoved);
}
else if (isImmediate)
{
layerToBeRemoved.TerminateLayer();
if (compositionLayersToBeUnsubscribed.Contains(layerToBeRemoved))
{
compositionLayersToBeUnsubscribed.Remove(layerToBeRemoved);
}
if (compositionLayersToBeSubscribed.Contains(layerToBeRemoved))
{
compositionLayersToBeSubscribed.Remove(layerToBeRemoved);
}
if (compositionLayers.Contains(layerToBeRemoved))
{
compositionLayers.Remove(layerToBeRemoved);
}
}
}
private void UpdateLayerStatus()
{
SortCompositionLayers();
RenderCompositionLayers();
}
private void SortCompositionLayers()
{
if (compositionLayers == null)
{
return;
}
CompositionLayerRenderPriorityComparer renderPriorityComparer = new CompositionLayerRenderPriorityComparer();
compositionLayers.Sort(renderPriorityComparer);
}
private void RenderCompositionLayers()
{
UpdateMaxLayerCount();
for (int layerIndex=0; layerIndex < compositionLayers.Count; layerIndex++)
{
if (layerIndex < maxLayerCount) //Render as normal layers
{
if (compositionLayers[layerIndex].RenderAsLayer()) //Successfully initialized
{
DEBUG("RenderCompositionLayers: Layer " + compositionLayers[layerIndex].name + " Initialized successfully, Priority: " + compositionLayers[layerIndex].GetRenderPriority() + " layerIndex: " + layerIndex);
}
else
{
DEBUG("RenderCompositionLayers: Layer Initialization failed." + " layerIndex: " + layerIndex);
}
}
else //Fallback if enabled
{
compositionLayers[layerIndex].RenderInGame();
DEBUG("RenderCompositionLayers: Layer " + compositionLayers[layerIndex].name + " Rendering in game, Priority: " + compositionLayers[layerIndex].GetRenderPriority() + " layerIndex: " + layerIndex);
}
}
}
private void UpdateMaxLayerCount()
{
XrSystemProperties xrSystemProperties = new XrSystemProperties();
XrResult result;
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
if (compositionLayerFeature != null)
{
if ((result = compositionLayerFeature.GetSystemProperties(ref xrSystemProperties)) == XrResult.XR_SUCCESS)
{
maxLayerCount = xrSystemProperties.graphicsProperties.maxLayerCount;
}
else
{
ERROR("Failed to get max layer count: " + result);
}
}
else
{
ERROR("compositionLayerFeature not found");
maxLayerCount = 0;
}
}
class CompositionLayerRenderPriorityComparer : IComparer<CompositionLayer>
{
public int Compare(CompositionLayer layerX, CompositionLayer layerY)
{
//Rule1: Higher Render Priority -> Front of the list
//Rule2: Same Render Priority -> Do not move layer
if (layerX.GetRenderPriority() > layerY.GetRenderPriority())
{
return -1;
}
else if (layerX.GetRenderPriority() < layerY.GetRenderPriority())
{
return 1;
}
else
{
return 0;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f9ba7acd7178cf4c9eabce25b055324
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,890 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using VIVE.OpenXR.CompositionLayer;
using VIVE.OpenXR.CompositionLayer.Passthrough;
namespace VIVE.OpenXR.CompositionLayer.Passthrough
{
[Obsolete("This class is deprecated. Please use PassthroughAPI instead.")]
public static class CompositionLayerPassthroughAPI
{
const string LOG_TAG = "CompositionLayerPassthroughAPI";
static void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
static void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); }
static void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
private static ViveCompositionLayerPassthrough passthroughFeature = null;
private static bool checkPassthroughFeatureInstance()
{
passthroughFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayerPassthrough>();
if (!passthroughFeature) return false;
return true;
}
#if UNITY_STANDALONE
private static Dictionary<int, XrCompositionLayerPassthroughHTC> passthrough2Layer = new Dictionary<int, XrCompositionLayerPassthroughHTC>();
private static Dictionary<int, IntPtr> passthrough2LayerPtr = new Dictionary<int, IntPtr>();
private static Dictionary<int, bool> passthrough2IsUnderLay= new Dictionary<int, bool>();
private static Dictionary<int, XrPassthroughMeshTransformInfoHTC> passthrough2meshTransform = new Dictionary<int, XrPassthroughMeshTransformInfoHTC>();
private static Dictionary<int, IntPtr> passthrough2meshTransformInfoPtr = new Dictionary<int, IntPtr>();
#endif
#region Public APIs
/// <summary>
/// For creating a fullscreen passthrough.
/// Passthroughs will be destroyed automatically when the current XrSession is destroyed.
/// </summary>
/// <returns>
/// ID of the created passthrough.
/// Value will be 0 if passthrough is not created successfully.
/// </returns>
/// <param name="layerType">
/// Specify whether the passthrough is an overlay or underlay. See <see cref="LayerType"/> for details.
/// </param>
/// <param name="onDestroyPassthroughSessionHandler">
/// Delegate of type <see cref="ViveCompositionLayerPassthrough.OnPassthroughSessionDestroyDelegate">OnPassthroughSessionDestroyDelegate</see>.
/// This delegate will be invoked when the current OpenXR Session is going to be destroyed, which is when the created passthrough layer should be destroyed if not.
/// </param>
/// <param name="alpha">
/// Specify the alpha of the passthrough layer.
/// Should be within range [0, 1]
/// 1 (Opaque) by default.
/// </param>
/// <param name="compositionDepth">
/// Specify the composition depth relative to other composition layers if present.
/// 0 by default.
/// </param>
public static int CreatePlanarPassthrough(LayerType layerType, ViveCompositionLayerPassthrough.OnPassthroughSessionDestroyDelegate onDestroyPassthroughSessionHandler = null, float alpha = 1f, uint compositionDepth = 0)
{
int passthroughID = 0;
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return passthroughID;
}
#if UNITY_ANDROID
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Planar, onDestroyPassthroughSessionHandler, compositionDepth);
#endif
#if UNITY_STANDALONE
XrPassthroughHTC passthrough = 0;
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PLANAR_HTC
);
XrResult res = passthroughFeature.CreatePassthroughHTC(createInfo, out passthrough);
if(res == XrResult.XR_SUCCESS)
{
ulong passthrough_ulong = passthrough;
passthroughID = (int)passthrough_ulong;
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
in_next: IntPtr.Zero,
in_alpha: alpha);
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
in_next: IntPtr.Zero,
in_layerFlags: ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
in_space: 0,
in_passthrough: passthrough,
in_color: passthroughColor);
passthrough2Layer.Add(passthroughID, compositionLayerPassthrough);
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
passthrough2LayerPtr.Add(passthroughID, layerPtr);
if (layerType == LayerType.Underlay)
passthrough2IsUnderLay.Add(passthroughID, true);
if (layerType == LayerType.Overlay)
passthrough2IsUnderLay.Add(passthroughID, false);
}
#endif
if (passthroughID == 0)
{
ERROR("Failed to create projected pasthrough");
}
else
{
SetPassthroughAlpha(passthroughID, alpha);
}
return passthroughID;
}
/// <summary>
/// For creating a projected passthrough (i.e. Passthrough is only partially visible).
/// Visible region of the projected passthrough is determined by the mesh and its transform.
/// Passthroughs will be destroyed automatically when the current XrSession is destroyed.
/// </summary>
/// <returns>
/// ID of the created passthrough.
/// Value will be 0 if passthrough is not created successfully.
/// </returns>
/// <param name="layerType">
/// Specify whether the passthrough is an overlay or underlay. See <see cref="LayerType"/> for details.
/// </param>
/// <param name="vertexBuffer">
/// Positions of the vertices in the mesh.
/// </param>
///<param name="indexBuffer">
/// List of triangles represented by indices into the <paramref name="vertexBuffer"/>.
/// </param>
/// <param name="spaceType">
/// Specify the type of space the projected passthrough is in. See <see cref="ProjectedPassthroughSpaceType"/> for details.
/// </param>
/// <param name="meshPosition">
/// Position of the mesh.
/// </param>
/// <param name="meshOrientation">
/// Orientation of the mesh.
/// </param>
/// <param name="meshScale">
/// Scale of the mesh.
/// </param>
/// <param name="onDestroyPassthroughSessionHandler">
/// Delegate of type <see cref="ViveCompositionLayerPassthrough.OnPassthroughSessionDestroyDelegate">OnPassthroughSessionDestroyDelegate</see>.
/// This delegate will be invoked when the current OpenXR Session is going to be destroyed, which is when the created passthrough layer should be destroyed if not.
/// </param>
/// <param name="alpha">
/// Specify the alpha of the passthrough layer.
/// Should be within range [0, 1]
/// 1 (Opaque) by default.
/// </param>
/// <param name="compositionDepth">
/// Specify the composition depth relative to other composition layers if present.
/// 0 by default.
/// </param>
/// <param name="trackingToWorldSpace">
/// Specify whether or not the position and rotation of the mesh transform have to be converted from tracking space to world space.
/// </param>
/// <param name="convertFromUnityToOpenXR">
/// Specify whether the parameters
/// <paramref name="vertexBuffer"/>, <paramref name="indexBuffer"/>, <paramref name="meshPosition"/> and <paramref name="meshOrientation"/> have to be converted for OpenXR.
/// </param>
public static int CreateProjectedPassthrough(LayerType layerType,
[In, Out] Vector3[] vertexBuffer, [In, Out] int[] indexBuffer, //For Mesh
ProjectedPassthroughSpaceType spaceType, Vector3 meshPosition, Quaternion meshOrientation, Vector3 meshScale, //For Mesh Transform
ViveCompositionLayerPassthrough.OnPassthroughSessionDestroyDelegate onDestroyPassthroughSessionHandler = null,
float alpha = 1f, uint compositionDepth = 0, bool trackingToWorldSpace = true, bool convertFromUnityToOpenXR = true)
{
int passthroughID = 0;
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return passthroughID;
}
if (vertexBuffer.Length < 3 || indexBuffer.Length % 3 != 0) //Must have at least 3 vertices and complete triangles
{
ERROR("Mesh data invalid.");
return passthroughID;
}
#if UNITY_STANDALONE
XrPassthroughHTC passthrough = 0;
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PROJECTED_HTC
);
XrResult res = passthroughFeature.CreatePassthroughHTC(createInfo, out passthrough);
if (res == XrResult.XR_SUCCESS)
{
ulong passthrough_ulong = passthrough;
passthroughID = (int)passthrough_ulong;
XrPassthroughMeshTransformInfoHTC PassthroughMeshTransformInfo = new XrPassthroughMeshTransformInfoHTC(
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC,
in_next: IntPtr.Zero,
in_vertexCount: 0,
in_vertices: new XrVector3f[0],
in_indexCount: 0,
in_indices: new UInt32[0],
in_baseSpace: XR_HTC_passthrough.Interop.GetTrackingSpace(),
in_time: XR_HTC_passthrough.Interop.GetFrameState().predictedDisplayTime,
in_pose: new XrPosef(),
in_scale: new XrVector3f()
);
IntPtr meshTransformInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrPassthroughMeshTransformInfoHTC)));
Marshal.StructureToPtr(PassthroughMeshTransformInfo, meshTransformInfoPtr, false);
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
in_next: IntPtr.Zero,
in_alpha: alpha);
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
in_next: meshTransformInfoPtr,
in_layerFlags: ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
in_space: 0,
in_passthrough: passthrough,
in_color: passthroughColor);
passthrough2meshTransform.Add(passthroughID, PassthroughMeshTransformInfo);
passthrough2meshTransformInfoPtr.Add(passthroughID, meshTransformInfoPtr);
passthrough2Layer.Add(passthroughID, compositionLayerPassthrough);
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
passthrough2LayerPtr.Add(passthroughID, layerPtr);
if (layerType == LayerType.Underlay)
passthrough2IsUnderLay.Add(passthroughID, true);
if (layerType == LayerType.Overlay)
passthrough2IsUnderLay.Add(passthroughID, false);
}
#endif
#if UNITY_ANDROID
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Projected, onDestroyPassthroughSessionHandler, compositionDepth);
#endif
if (passthroughID == 0)
{
ERROR("Failed to create projected pasthrough");
}
else
{
SetPassthroughAlpha(passthroughID, alpha);
SetProjectedPassthroughMesh(passthroughID, vertexBuffer, indexBuffer, convertFromUnityToOpenXR);
SetProjectedPassthroughMeshTransform(passthroughID, spaceType, meshPosition, meshOrientation, meshScale, trackingToWorldSpace, convertFromUnityToOpenXR);
}
return passthroughID;
}
/// <summary>
/// Creating a projected passthrough (i.e. Passthrough is only partially visible).
/// Visible region of the projected passthrough is determined by the mesh and its transform.
/// Passthroughs will be destroyed automatically when the current XrSession is destroyed.
/// </summary>
/// <remarks>
/// When using this overload, <see cref="SetProjectedPassthroughMesh"/> and <see cref="SetProjectedPassthroughMeshTransform"/> must be called afterwards immediately.
/// </remarks>
/// <example>
/// <code>
/// int PassthroughID = CompositionLayerPassthroughAPI.CreateProjectedPassthrough(layerType, passthroughSessionDestroyHandler, alpha);
/// CompositionLayerPassthroughAPI.SetProjectedPassthroughMesh(PassthroughID, quadVertices, quadIndicies, true);
/// CompositionLayerPassthroughAPI.SetProjectedPassthroughMeshTranform(PassthroughID, spaceType, position, rotation, scale, true);
/// </code>
/// </example>
/// <returns>
/// ID of the created passthrough.
/// Value will be 0 if passthrough is not created successfully.
/// </returns>
/// <param name="layerType">
/// Specify whether the passthrough is an overlay or underlay. See <see cref="LayerType"/> for details.
/// </param>
/// <param name="onDestroyPassthroughSessionHandler">
/// Delegate of type <see cref="ViveCompositionLayerPassthrough.OnPassthroughSessionDestroyDelegate">OnPassthroughSessionDestroyDelegate</see>.
/// This delegate will be invoked when the current OpenXR Session is going to be destroyed, which is when the created passthrough layer should be destroyed if not.
/// </param>
/// <param name="alpha">
/// Specify the alpha of the passthrough layer.
/// Should be within range [0, 1].
/// 1 (Opaque) by default.
/// </param>
/// <param name="compositionDepth">
/// Specify the composition depth relative to other composition layers if present.
/// 0 by default.
/// </param>
public static int CreateProjectedPassthrough(LayerType layerType, ViveCompositionLayerPassthrough.OnPassthroughSessionDestroyDelegate onDestroyPassthroughSessionHandler = null, float alpha = 1f, uint compositionDepth = 0)
{
int passthroughID = 0;
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return passthroughID;
}
#if UNITY_STANDALONE
XrPassthroughHTC passthrough = 0;
XrPassthroughCreateInfoHTC createInfo = new XrPassthroughCreateInfoHTC(
XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC,
new IntPtr(6), //Enter IntPtr(0) for backward compatibility (using createPassthrough to enable the passthrough feature), or enter IntPtr(6) to enable the passthrough feature based on the layer submitted to endframe.
XrPassthroughFormHTC.XR_PASSTHROUGH_FORM_PROJECTED_HTC
);
XrResult res = passthroughFeature.CreatePassthroughHTC(createInfo, out passthrough);
if (res == XrResult.XR_SUCCESS)
{
ulong passthrough_ulong = passthrough;
passthroughID = (int)passthrough_ulong;
XrPassthroughMeshTransformInfoHTC PassthroughMeshTransformInfo = new XrPassthroughMeshTransformInfoHTC(
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC,
in_next: IntPtr.Zero,
in_vertexCount: 0,
in_vertices: new XrVector3f[0],
in_indexCount: 0,
in_indices: new UInt32[0],
in_baseSpace: XR_HTC_passthrough.Interop.GetTrackingSpace(),
in_time: XR_HTC_passthrough.Interop.GetFrameState().predictedDisplayTime,
in_pose: new XrPosef(),
in_scale: new XrVector3f()
);
IntPtr meshTransformInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrPassthroughMeshTransformInfoHTC)));
Marshal.StructureToPtr(PassthroughMeshTransformInfo, meshTransformInfoPtr, false);
XrPassthroughColorHTC passthroughColor = new XrPassthroughColorHTC(
in_type: XrStructureType.XR_TYPE_PASSTHROUGH_COLOR_HTC,
in_next: IntPtr.Zero,
in_alpha: alpha);
XrCompositionLayerPassthroughHTC compositionLayerPassthrough = new XrCompositionLayerPassthroughHTC(
in_type: XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC,
in_next: meshTransformInfoPtr,
in_layerFlags: ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT,
in_space: 0,
in_passthrough: passthrough,
in_color: passthroughColor);
passthrough2meshTransform.Add(passthroughID, PassthroughMeshTransformInfo);
passthrough2meshTransformInfoPtr.Add(passthroughID, meshTransformInfoPtr);
passthrough2Layer.Add(passthroughID, compositionLayerPassthrough);
IntPtr layerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XrCompositionLayerPassthroughHTC)));
passthrough2LayerPtr.Add(passthroughID, layerPtr);
if (layerType == LayerType.Underlay)
passthrough2IsUnderLay.Add(passthroughID, true);
if (layerType == LayerType.Overlay)
passthrough2IsUnderLay.Add(passthroughID, false);
}
#endif
#if UNITY_ANDROID
passthroughID = passthroughFeature.HTCPassthrough_CreatePassthrough(layerType, PassthroughLayerForm.Projected, onDestroyPassthroughSessionHandler);
#endif
if (passthroughID == 0)
{
ERROR("Failed to create projected pasthrough");
}
else
{
SetPassthroughAlpha(passthroughID, alpha);
}
return passthroughID;
}
#if UNITY_STANDALONE
private static void SubmitLayer()
{
XR_HTC_passthrough.Interop.GetOriginEndFrameLayerList(out List<IntPtr> layerList);//GetOriginEndFrameLayers
foreach(var passthrough in passthrough2IsUnderLay.Keys)
{
//Get and submit layer list
if (layerList.Count != 0)
{
Marshal.StructureToPtr(passthrough2Layer[passthrough], passthrough2LayerPtr[passthrough], false);
if (passthrough2IsUnderLay[passthrough])
layerList.Insert(0, passthrough2LayerPtr[passthrough]);
else
layerList.Insert(1, passthrough2LayerPtr[passthrough]);
}
}
XR_HTC_passthrough.Interop.SubmitLayers(layerList);
}
#endif
/// <summary>
/// For destroying a passthrough created previously.
/// This function should be called in the delegate instance of type <see cref="ViveCompositionLayerPassthrough.OnPassthroughSessionDestroyDelegate">OnPassthroughSessionDestroyDelegate</see> that is previously assigned when creating a passthrough.
/// </summary>
/// <returns>
/// True for successfully destroying the specified passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the passthrough to be destroyed.
/// </param>
public static bool DestroyPassthrough(int passthroughID)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
if (!passthroughFeature.PassthroughIDList.Contains(passthroughID))
{
ERROR("Passthrough to be destroyed not found");
return false;
}
#if UNITY_STANDALONE
XrPassthroughHTC passthrough = passthrough2Layer[passthroughID].passthrough;
passthroughFeature.DestroyPassthroughHTC(passthrough);
passthrough2IsUnderLay.Remove(passthroughID);
SubmitLayer();
passthrough2Layer.Remove(passthroughID);
if(passthrough2LayerPtr.ContainsKey(passthroughID)) Marshal.FreeHGlobal(passthrough2LayerPtr[passthroughID]);
passthrough2LayerPtr.Remove(passthroughID);
if(passthrough2meshTransformInfoPtr.ContainsKey(passthroughID)) Marshal.FreeHGlobal(passthrough2meshTransformInfoPtr[passthroughID]);
passthrough2meshTransformInfoPtr.Remove(passthroughID);
passthrough2meshTransform.Remove(passthroughID);
return true;
#endif
#if UNITY_ANDROID
return passthroughFeature.HTCPassthrough_DestroyPassthrough(passthroughID);
#endif
}
/// <summary>
/// For modifying the opacity of a specific passthrough layer.
/// Can be used for both Planar and Projected passthroughs.
/// </summary>
/// <returns>
/// True for successfully modifying the opacity the specified passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the target passthrough.
/// </param>
/// <param name="alpha">
/// Specify the alpha of the passthrough layer.
/// Should be within range [0, 1]
/// 1 (Opaque) by default.
/// </param>
/// <param name="autoClamp">
/// Specify whether out of range alpha values should be clamped automatically.
/// When set to true, the function will clamp and apply the alpha value automatically.
/// When set to false, the function will return false if the alpha is out of range.
/// Set to true by default.
/// </param>
public static bool SetPassthroughAlpha(int passthroughID, float alpha, bool autoClamp = true)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
#if UNITY_ANDROID
if (autoClamp)
{
return passthroughFeature.HTCPassthrough_SetAlpha(passthroughID, Mathf.Clamp01(alpha));
}
else
{
if (alpha < 0f || alpha > 1f)
{
ERROR("SetPassthroughAlpha: Alpha out of range");
return false;
}
return passthroughFeature.HTCPassthrough_SetAlpha(passthroughID, alpha);
}
#endif
#if UNITY_STANDALONE
if (passthrough2Layer.ContainsKey(passthroughID))
{
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
layer.color.alpha = alpha;
passthrough2Layer[passthroughID] = layer;
SubmitLayer();
return true;
}
else
return false;
#endif
}
/// <summary>
/// For modifying the mesh data of a projected passthrough layer.
/// </summary>
/// <returns>
/// True for successfully modifying the mesh data of the projected passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the target passthrough.
/// </param>
/// <param name="vertexBuffer">
/// Positions of the vertices in the mesh.
/// </param>
///<param name="indexBuffer">
/// List of triangles represented by indices into the <paramref name="vertexBuffer"/>.
/// </param>
/// <param name="convertFromUnityToOpenXR">
/// Specify whether the parameters
/// <paramref name="vertexBuffer"/> and <paramref name="indexBuffer"/> have to be converted for OpenXR.
/// </param>
public static bool SetProjectedPassthroughMesh(int passthroughID, [In, Out] Vector3[] vertexBuffer, [In, Out] int[] indexBuffer, bool convertFromUnityToOpenXR = true)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
if (vertexBuffer.Length < 3 || indexBuffer.Length % 3 != 0) //Must have at least 3 vertices and complete triangles
{
ERROR("Mesh data invalid.");
return false;
}
XrVector3f[] vertexBufferXrVector = new XrVector3f[vertexBuffer.Length];
for (int i = 0; i < vertexBuffer.Length; i++)
{
vertexBufferXrVector[i] = OpenXRHelper.ToOpenXRVector(vertexBuffer[i], convertFromUnityToOpenXR);
}
uint[] indexBufferUint = new uint[indexBuffer.Length];
for (int i = 0; i < indexBuffer.Length; i++)
{
indexBufferUint[i] = (uint)indexBuffer[i];
}
#if UNITY_STANDALONE
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
{
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
MeshTransformInfo.vertexCount = (uint)vertexBuffer.Length;
MeshTransformInfo.vertices = vertexBufferXrVector;
MeshTransformInfo.indexCount = (uint)indexBuffer.Length;
MeshTransformInfo.indices = indexBufferUint;
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
passthrough2Layer[passthroughID] = layer;
SubmitLayer();
return true;
}
else
return false;
#endif
//Note: Ignore Clock-Wise definition of index buffer for now as passthrough extension does not have back-face culling
#if UNITY_ANDROID
return passthroughFeature.HTCPassthrough_SetMesh(passthroughID, (uint)vertexBuffer.Length, vertexBufferXrVector, (uint)indexBuffer.Length, indexBufferUint); ;
#endif
}
/// <summary>
/// For modifying the mesh transform of a projected passthrough layer.
/// </summary>
/// <returns>
/// True for successfully modifying the mesh data of the projected passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the target passthrough.
/// </param>
/// <param name="spaceType">
/// Specify the type of space the projected passthrough is in. See <see cref="ProjectedPassthroughSpaceType"/> for details.
/// </param>
/// <param name="meshPosition">
/// Position of the mesh.
/// </param>
/// <param name="meshOrientation">
/// Orientation of the mesh.
/// </param>
/// <param name="meshScale">
/// Scale of the mesh.
/// </param>
/// <param name="trackingToWorldSpace">
/// Specify whether or not the position and rotation of the mesh transform have to be converted from tracking space to world space.
/// </param>
/// <param name="convertFromUnityToOpenXR">
/// Specify whether the parameters
/// <paramref name="meshPosition"/> and <paramref name="meshOrientation"/> have to be converted for OpenXR.
/// </param>
public static bool SetProjectedPassthroughMeshTransform(int passthroughID, ProjectedPassthroughSpaceType spaceType, Vector3 meshPosition, Quaternion meshOrientation, Vector3 meshScale, bool trackingToWorldSpace = true, bool convertFromUnityToOpenXR = true)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
Vector3 trackingSpaceMeshPosition = meshPosition;
Quaternion trackingSpaceMeshRotation = meshOrientation;
TrackingSpaceOrigin currentTrackingSpaceOrigin = TrackingSpaceOrigin.Instance;
if (currentTrackingSpaceOrigin != null && trackingToWorldSpace) //Apply origin correction to the mesh pose
{
Matrix4x4 trackingSpaceOriginTRS = Matrix4x4.TRS(currentTrackingSpaceOrigin.transform.position, currentTrackingSpaceOrigin.transform.rotation, Vector3.one);
Matrix4x4 worldSpaceLayerPoseTRS = Matrix4x4.TRS(meshPosition, meshOrientation, Vector3.one);
Matrix4x4 trackingSpaceLayerPoseTRS = trackingSpaceOriginTRS.inverse * worldSpaceLayerPoseTRS;
trackingSpaceMeshPosition = trackingSpaceLayerPoseTRS.GetColumn(3); //4th Column of TRS Matrix is the position
trackingSpaceMeshRotation = Quaternion.LookRotation(trackingSpaceLayerPoseTRS.GetColumn(2), trackingSpaceLayerPoseTRS.GetColumn(1));
}
XrPosef meshXrPose;
meshXrPose.position = OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR);
meshXrPose.orientation = OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR);
XrVector3f meshXrScale = OpenXRHelper.ToOpenXRVector(meshScale, false);
#if UNITY_STANDALONE
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
{
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
MeshTransformInfo.pose = meshXrPose;
MeshTransformInfo.scale = meshXrScale;
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
passthrough2Layer[passthroughID] = layer;
SubmitLayer();
return true;
}
else
return false;
#endif
#if UNITY_ANDROID
return passthroughFeature.HTCPassthrough_SetMeshTransform(passthroughID, passthroughFeature.GetXrSpaceFromSpaceType(spaceType), meshXrPose, meshXrScale);
#endif
}
/// <summary>
/// For modifying layer type and composition depth of a passthrough layer.
/// </summary>
/// <returns>
/// True for successfully modifying the layer type and composition depth of the passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the target passthrough.
/// </param>
/// <param name="layerType">
/// Specify whether the passthrough is an overlay or underlay. See <see cref="LayerType"/> for details.
/// </param>
/// <param name="compositionDepth">
/// Specify the composition depth relative to other composition layers if present.
/// 0 by default.
/// </param>
public static bool SetPassthroughLayerType(int passthroughID, LayerType layerType, uint compositionDepth = 0)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
#if UNITY_STANDALONE
if (passthrough2IsUnderLay.ContainsKey(passthroughID))
{
passthrough2IsUnderLay[passthroughID] = layerType == LayerType.Underlay ? true : false;
SubmitLayer();
return true;
}
else
return false;
#endif
#if UNITY_ANDROID
return passthroughFeature.HTCPassthrough_SetLayerType(passthroughID, layerType, compositionDepth);
#endif
}
/// <summary>
/// For modifying the space of a projected passthrough layer.
/// </summary>
/// <returns>
/// True for successfully modifying the space of the projected passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the target passthrough.
/// </param>
/// <param name="spaceType">
/// Specify the type of space the projected passthrough is in. See <see cref="ProjectedPassthroughSpaceType"/> for details.
/// </param>
public static bool SetProjectedPassthroughSpaceType(int passthroughID, ProjectedPassthroughSpaceType spaceType)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
#if UNITY_STANDALONE
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
{
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
MeshTransformInfo.baseSpace = passthroughFeature.GetXrSpaceFromSpaceType(spaceType);
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
passthrough2Layer[passthroughID] = layer;
SubmitLayer();
return true;
}
else
return false;
#endif
#if UNITY_ANDROID
return passthroughFeature.HTCPassthrough_SetMeshTransformSpace(passthroughID, passthroughFeature.GetXrSpaceFromSpaceType(spaceType));
#endif
}
/// <summary>
/// For modifying the mesh position of a projected passthrough layer.
/// </summary>
/// <returns>
/// True for successfully modifying the mesh position of the projected passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the target passthrough.
/// </param>
/// <param name="meshPosition">
/// Position of the mesh.
/// </param>
/// <param name="trackingToWorldSpace">
/// Specify whether or not the position of the mesh transform have to be converted from tracking space to world space.
/// </param>
/// <param name="convertFromUnityToOpenXR">
/// Specify whether the parameter
/// <paramref name="meshPosition"/> have to be converted for OpenXR.
/// </param>
public static bool SetProjectedPassthroughMeshPosition(int passthroughID, Vector3 meshPosition, bool trackingToWorldSpace = true, bool convertFromUnityToOpenXR = true)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
Vector3 trackingSpaceMeshPosition = meshPosition;
TrackingSpaceOrigin currentTrackingSpaceOrigin = TrackingSpaceOrigin.Instance;
if (currentTrackingSpaceOrigin != null && trackingToWorldSpace) //Apply origin correction to the mesh pose
{
Matrix4x4 trackingSpaceOriginTRS = Matrix4x4.TRS(currentTrackingSpaceOrigin.transform.position, Quaternion.identity, Vector3.one);
Matrix4x4 worldSpaceLayerPoseTRS = Matrix4x4.TRS(meshPosition, Quaternion.identity, Vector3.one);
Matrix4x4 trackingSpaceLayerPoseTRS = trackingSpaceOriginTRS.inverse * worldSpaceLayerPoseTRS;
trackingSpaceMeshPosition = trackingSpaceLayerPoseTRS.GetColumn(3); //4th Column of TRS Matrix is the position
}
#if UNITY_STANDALONE
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
{
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
XrPosef meshXrPose = MeshTransformInfo.pose;
meshXrPose.position = OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR); ;
MeshTransformInfo.pose = meshXrPose;
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
passthrough2Layer[passthroughID] = layer;
SubmitLayer();
return true;
}
else
return false;
#endif
#if UNITY_ANDROID
return passthroughFeature.HTCPassthrough_SetMeshTransformPosition(passthroughID, OpenXRHelper.ToOpenXRVector(trackingSpaceMeshPosition, convertFromUnityToOpenXR));
#endif
}
/// <summary>
/// For modifying the mesh orientation of a projected passthrough layer.
/// </summary>
/// <returns>
/// True for successfully modifying the mesh orientation of the projected passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the target passthrough.
/// </param>
/// <param name="meshOrientation">
/// Orientation of the mesh.
/// </param>
/// <param name="trackingToWorldSpace">
/// Specify whether or not the rotation of the mesh transform have to be converted from tracking space to world space.
/// </param>
/// <param name="convertFromUnityToOpenXR">
/// Specify whether the parameter
/// <paramref name="meshOrientation"/> have to be converted for OpenXR.
/// </param>
public static bool SetProjectedPassthroughMeshOrientation(int passthroughID, Quaternion meshOrientation, bool trackingToWorldSpace = true, bool convertFromUnityToOpenXR = true)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
Quaternion trackingSpaceMeshRotation = meshOrientation;
TrackingSpaceOrigin currentTrackingSpaceOrigin = TrackingSpaceOrigin.Instance;
if (currentTrackingSpaceOrigin != null && trackingToWorldSpace) //Apply origin correction to the mesh pose
{
Matrix4x4 trackingSpaceOriginTRS = Matrix4x4.TRS(Vector3.zero, currentTrackingSpaceOrigin.transform.rotation, Vector3.one);
Matrix4x4 worldSpaceLayerPoseTRS = Matrix4x4.TRS(Vector3.zero, meshOrientation, Vector3.one);
Matrix4x4 trackingSpaceLayerPoseTRS = trackingSpaceOriginTRS.inverse * worldSpaceLayerPoseTRS;
trackingSpaceMeshRotation = Quaternion.LookRotation(trackingSpaceLayerPoseTRS.GetColumn(2), trackingSpaceLayerPoseTRS.GetColumn(1));
}
#if UNITY_STANDALONE
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
{
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
XrPosef meshXrPose = MeshTransformInfo.pose;
meshXrPose.orientation = OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR);
MeshTransformInfo.pose = meshXrPose;
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
passthrough2Layer[passthroughID] = layer;
SubmitLayer();
return true;
}
else
return false;
#endif
#if UNITY_ANDROID
return passthroughFeature.HTCPassthrough_SetMeshTransformOrientation(passthroughID, OpenXRHelper.ToOpenXRQuaternion(trackingSpaceMeshRotation, convertFromUnityToOpenXR));
#endif
}
/// <summary>
/// For modifying the mesh scale of a passthrough layer.
/// </summary>
/// <returns>
/// True for successfully modifying the mesh data of the projected passthrough, vice versa.
/// </returns>
/// <param name="passthroughID">
/// The ID of the target passthrough.
/// </param>
/// <param name="meshScale">
/// Scale of the mesh.
/// </param>
public static bool SetProjectedPassthroughScale(int passthroughID, Vector3 meshScale)
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return false;
}
#if UNITY_STANDALONE
if (passthrough2meshTransformInfoPtr.ContainsKey(passthroughID))
{
XrPassthroughMeshTransformInfoHTC MeshTransformInfo = passthrough2meshTransform[passthroughID];
MeshTransformInfo.scale = OpenXRHelper.ToOpenXRVector(meshScale, false);
passthrough2meshTransform[passthroughID] = MeshTransformInfo;
Marshal.StructureToPtr(MeshTransformInfo, passthrough2meshTransformInfoPtr[passthroughID], false);
XrCompositionLayerPassthroughHTC layer = passthrough2Layer[passthroughID];
layer.next = passthrough2meshTransformInfoPtr[passthroughID];
passthrough2Layer[passthroughID] = layer;
SubmitLayer();
return true;
}
else
return false;
#endif
#if UNITY_ANDROID
return passthroughFeature.HTCPassthrough_SetMeshTransformScale(passthroughID, OpenXRHelper.ToOpenXRVector(meshScale, false));
#endif
}
/// <summary>
/// To get the list of IDs of active passthrough layers.
/// </summary>
/// <returns>
/// The a copy of the list of IDs of active passthrough layers.
/// </returns>
public static List<int> GetCurrentPassthroughLayerIDs()
{
if (!checkPassthroughFeatureInstance())
{
ERROR("HTC_Passthrough feature instance not found.");
return null;
}
return passthroughFeature.PassthroughIDList;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 84f10e5139d365a4dafe943a2d80829b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,177 @@
// Copyright HTC Corporation All Rights Reserved.
using AOT;
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
namespace VIVE.OpenXR.CompositionLayer
{
public class CompositionLayerRenderThreadSyncObject
{
private static IntPtr GetFunctionPointerForDelegate(Delegate del)
{
#if UNITY_EDITOR && UNITY_ANDROID
return IntPtr.Zero;
#elif UNITY_ANDROID
return Marshal.GetFunctionPointerForDelegate(del);
#else
return IntPtr.Zero;
#endif
}
public delegate void CompositionLayerRenderEventDelegate(int eventID);
private static readonly CompositionLayerRenderEventDelegate handle = new CompositionLayerRenderEventDelegate(RunSyncObjectInRenderThread);
private static readonly IntPtr handlePtr = GetFunctionPointerForDelegate(handle);
public delegate void TaskQueueDelagate(PreAllocatedQueue taskQueue);
private static List<CompositionLayerRenderThreadSyncObject> taskList = new List<CompositionLayerRenderThreadSyncObject>();
private readonly PreAllocatedQueue queue = new PreAllocatedQueue();
public PreAllocatedQueue Queue { get { return queue; } }
private readonly TaskQueueDelagate receiver;
private readonly int taskID;
public CompositionLayerRenderThreadSyncObject(TaskQueueDelagate taskQueueDelegate)
{
receiver = taskQueueDelegate;
if (receiver == null)
throw new ArgumentNullException("receiver should not be null");
taskList.Add(this);
taskID = taskList.IndexOf(this);
}
~CompositionLayerRenderThreadSyncObject()
{
try { taskList.RemoveAt(taskID); } finally { }
}
[MonoPInvokeCallback(typeof(CompositionLayerRenderEventDelegate))]
private static void RunSyncObjectInRenderThread(int taskID)
{
taskList[taskID].ReceiveEvent();
}
// Run in GameThread
public void IssueEvent()
{
#if UNITY_EDITOR && UNITY_ANDROID
if (Application.isEditor)
{
receiver(queue);
return;
}
#endif
// Let the render thread run the RunSyncObjectInRenderThread(id)
#if UNITY_ANDROID
GL.IssuePluginEvent(handlePtr, taskID);
#else
receiver(queue);
return;
#endif
}
private void ReceiveEvent()
{
receiver(queue);
}
}
public class Task
{
public bool isFree = true;
}
public class TaskPool
{
private readonly List<Task> pool = new List<Task>(2) { };
private int index = 0;
public TaskPool() { }
private int Next(int value)
{
if (++value >= pool.Count)
value = 0;
return value;
}
public T Obtain<T>() where T : Task, new()
{
int c = pool.Count;
int i = index;
for (int j = 0; j < c; i++, j++)
{
if (i >= c)
i = 0;
if (pool[i].isFree)
{
//Debug.LogError("Obtain idx=" + i);
index = i;
return (T)pool[i];
}
}
index = Next(i);
var newItem = new T()
{
isFree = true
};
pool.Insert(index, newItem);
//Debug.LogError("Obtain new one. Pool.Count=" + pool.Count);
return newItem;
}
public void Lock(Task msg)
{
msg.isFree = false;
}
public void Release(Task msg)
{
msg.isFree = true;
}
}
public class PreAllocatedQueue : TaskPool
{
private readonly List<Task> list = new List<Task>(2) { null, null };
private int queueBegin = 0;
private int queueEnd = 0;
public PreAllocatedQueue() : base() { }
private int Next(int value)
{
if (++value >= list.Count)
value = 0;
return value;
}
public void Enqueue(Task msg)
{
Lock(msg);
queueEnd = Next(queueEnd);
if (queueEnd == queueBegin)
{
list.Insert(queueEnd, msg);
queueBegin++;
}
else
{
list[queueEnd] = msg;
}
}
public Task Dequeue()
{
queueBegin = Next(queueBegin);
return list[queueBegin];
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5edefaf3751462141bc4fa0c0eed4c08
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,293 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
namespace VIVE.OpenXR.CompositionLayer
{
[RequireComponent(typeof(Canvas))]
public class CompositionLayerUICanvas : MonoBehaviour
{
private Canvas sourceCanvas;
private RectTransform sourceCanvasRectTransform;
private Graphic[] graphicComponents;
private Camera canvasRenderCamera;
private RenderTexture canvasRenderTexture;
private GameObject canvasCompositionLayerGO;
private CompositionLayer canvasCompositionLayer;
[SerializeField]
public uint maxRenderTextureSize = 1024;
[SerializeField]
public CompositionLayer.LayerType layerType = CompositionLayer.LayerType.Underlay;
[SerializeField]
public CompositionLayer.Visibility layerVisibility = CompositionLayer.Visibility.Both;
[SerializeField]
public Color cameraBGColor = Color.clear;
[SerializeField]
public List<GameObject> backgroundGO = new List<GameObject>();
[SerializeField]
public bool enableAlphaBlendingCorrection = false;
[SerializeField]
public uint compositionDepth = 0;
[SerializeField]
private uint renderPriority = 0;
public uint GetRenderPriority() { return renderPriority; }
public void SetRenderPriority(uint newRenderPriority)
{
renderPriority = newRenderPriority;
canvasCompositionLayer.SetRenderPriority(renderPriority);
}
[SerializeField]
public GameObject trackingOrigin = null;
private CompositionLayer.LayerType previousLayerType;
private CompositionLayer.Visibility previousLayerVisibility;
private uint previousCompositionDepth;
private GameObject previousTrackingOrigin;
private void Start()
{
sourceCanvas = GetComponent<Canvas>();
sourceCanvasRectTransform = sourceCanvas.GetComponent<RectTransform>();
UpdateUIElementBlendMode();
//Calulate Aspect Ratio of the Canvas
float canvasRectWidth = sourceCanvasRectTransform.rect.width;
float canvasRectHeight = sourceCanvasRectTransform.rect.height;
float canvasAspectRatio_X = 1, canvasAspectRatio_Y = 1;
if (canvasRectWidth > canvasRectHeight)
{
canvasAspectRatio_X = canvasRectWidth / canvasRectHeight;
}
else if (canvasRectWidth < canvasRectHeight)
{
canvasAspectRatio_Y = canvasRectHeight / canvasRectWidth;
}
//Create Render Texture
int renderTextureWidth = Mathf.CeilToInt(maxRenderTextureSize * canvasAspectRatio_X);
int renderTextureHeight = Mathf.CeilToInt(maxRenderTextureSize * canvasAspectRatio_Y);
canvasRenderTexture = new RenderTexture(renderTextureWidth, renderTextureHeight, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default);
canvasRenderTexture.useMipMap = false;
canvasRenderTexture.filterMode = FilterMode.Bilinear;
canvasRenderTexture.autoGenerateMips = false;
canvasRenderTexture.Create();
//Create Canvas Rendering Camera
GameObject canvasRenderCameraGO = new GameObject(name + "_CanvasRenderCamera");
canvasRenderCameraGO.transform.SetParent(transform, false);
canvasRenderCamera = canvasRenderCameraGO.AddComponent<Camera>();
canvasRenderCamera.stereoTargetEye = StereoTargetEyeMask.None;
canvasRenderCamera.transform.position = transform.position - transform.forward; //1m away from canvas
canvasRenderCamera.orthographic = true;
canvasRenderCamera.enabled = false;
canvasRenderCamera.targetTexture = canvasRenderTexture;
canvasRenderCamera.cullingMask = 1 << gameObject.layer;
canvasRenderCamera.clearFlags = CameraClearFlags.SolidColor;
canvasRenderCamera.backgroundColor = cameraBGColor;
float widthWithScale = canvasRectWidth * sourceCanvasRectTransform.localScale.x;
float heightWithScale = canvasRectHeight * sourceCanvasRectTransform.localScale.y;
canvasRenderCamera.orthographicSize = 0.5f * heightWithScale;
canvasRenderCamera.nearClipPlane = 0.99f;
canvasRenderCamera.farClipPlane = 1.01f;
//Create Composition Layer Component
canvasCompositionLayerGO = new GameObject(name + "_CanvasCompositionLayer");
canvasCompositionLayerGO.transform.SetParent(transform, false);
canvasCompositionLayerGO.transform.localPosition = Vector3.zero;
canvasCompositionLayerGO.transform.localRotation = Quaternion.identity;
canvasCompositionLayerGO.transform.localScale = Vector3.one;
canvasCompositionLayer = canvasCompositionLayerGO.AddComponent<CompositionLayer>();
canvasCompositionLayer.isDynamicLayer = true;
canvasCompositionLayer.texture = canvasRenderTexture;
canvasCompositionLayer.layerShape = CompositionLayer.LayerShape.Quad;
canvasCompositionLayer.layerType = previousLayerType = layerType;
canvasCompositionLayer.layerVisibility = previousLayerVisibility = layerVisibility;
canvasCompositionLayer.SetQuadLayerHeight(heightWithScale);
canvasCompositionLayer.SetQuadLayerWidth(widthWithScale);
canvasCompositionLayer.compositionDepth = previousCompositionDepth = compositionDepth;
canvasCompositionLayer.SetRenderPriority(renderPriority);
canvasCompositionLayer.trackingOrigin = previousTrackingOrigin = trackingOrigin;
if (enableAlphaBlendingCorrection && layerType == CompositionLayer.LayerType.Underlay)
{
canvasCompositionLayer.ChangeBlitShadermode(CompositionLayer.BlitShaderMode.LINEAR_TO_SRGB_ALPHA, true);
}
else
{
canvasCompositionLayer.ChangeBlitShadermode(CompositionLayer.BlitShaderMode.LINEAR_TO_SRGB_ALPHA, false);
}
}
private void Update()
{
canvasRenderCamera.Render();
if (layerType != previousLayerType)
{
canvasCompositionLayer.layerType = previousLayerType = layerType;
if (enableAlphaBlendingCorrection && layerType == CompositionLayer.LayerType.Underlay)
{
canvasCompositionLayer.ChangeBlitShadermode(CompositionLayer.BlitShaderMode.LINEAR_TO_SRGB_ALPHA, true);
}
else
{
canvasCompositionLayer.ChangeBlitShadermode(CompositionLayer.BlitShaderMode.LINEAR_TO_SRGB_ALPHA, false);
}
}
if (layerVisibility != previousLayerVisibility)
{
canvasCompositionLayer.layerVisibility = previousLayerVisibility = layerVisibility;
}
if (compositionDepth != previousCompositionDepth)
{
canvasCompositionLayer.compositionDepth = previousCompositionDepth = compositionDepth;
}
if (trackingOrigin != previousTrackingOrigin)
{
canvasCompositionLayer.trackingOrigin = previousTrackingOrigin = trackingOrigin;
}
if (canvasRenderCamera.backgroundColor != cameraBGColor)
{
canvasRenderCamera.backgroundColor = cameraBGColor;
}
}
private void OnDestroy()
{
if (canvasRenderTexture != null && canvasRenderTexture.IsCreated())
{
canvasRenderTexture.Release();
Destroy(canvasRenderTexture);
}
if (canvasCompositionLayerGO)
{
Destroy(canvasCompositionLayerGO);
}
}
private void OnEnable()
{
if (canvasRenderCamera)
{
canvasRenderCamera.enabled = true;
}
if (canvasCompositionLayerGO)
{
canvasCompositionLayerGO.SetActive(true);
}
}
private void OnDisable()
{
if (canvasRenderCamera)
{
canvasRenderCamera.enabled = false;
}
if (canvasCompositionLayerGO)
{
canvasCompositionLayerGO.SetActive(false);
}
}
public void ReplaceUIMaterials()
{
sourceCanvas = GetComponent<Canvas>();
graphicComponents = sourceCanvas.GetComponentsInChildren<Graphic>();
Material underlayCanvasUIMat = new Material(Shader.Find("VIVE/OpenXR/CompositionLayerUICanvas/MultiLayerCanvasUI"));
foreach (Graphic graphicComponent in graphicComponents)
{
if (backgroundGO != null && backgroundGO.Contains(graphicComponent.gameObject))
{
graphicComponent.material = new Material(Shader.Find("VIVE/OpenXR/CompositionLayerUICanvas/MultiLayerCanvasUI")); //Seperate material instance for background
}
else
{
graphicComponent.material = underlayCanvasUIMat;
}
}
}
public void UpdateUIElementBlendMode()
{
sourceCanvas = GetComponent<Canvas>();
graphicComponents = sourceCanvas.GetComponentsInChildren<Graphic>();
foreach (Graphic graphicComponent in graphicComponents)
{
if (backgroundGO != null && backgroundGO.Contains(graphicComponent.gameObject))
{
SetUIShaderBlendMode(graphicComponent.material, UIShaderBlendMode.Background);
}
else
{
SetUIShaderBlendMode(graphicComponent.material, UIShaderBlendMode.Others);
}
}
}
public void SetUIShaderBlendMode(Material canvasUIMaterial, UIShaderBlendMode blendMode = UIShaderBlendMode.Others)
{
switch (blendMode)
{
case UIShaderBlendMode.Background: //Discard camera background color and alpha values
canvasUIMaterial.SetInt("_SrcColBlendMode", (int)BlendMode.One);
canvasUIMaterial.SetInt("_DstColBlendMode", (int)BlendMode.Zero);
canvasUIMaterial.SetInt("_SrcAlpBlendMode", (int)BlendMode.One);
canvasUIMaterial.SetInt("_DstAlpBlendMode", (int)BlendMode.Zero);
break;
case UIShaderBlendMode.Others: //Nornmal transparency blending
default:
canvasUIMaterial.SetInt("_SrcColBlendMode", (int)BlendMode.SrcAlpha);
canvasUIMaterial.SetInt("_DstColBlendMode", (int)BlendMode.OneMinusSrcAlpha);
canvasUIMaterial.SetInt("_SrcAlpBlendMode", (int)BlendMode.One);
canvasUIMaterial.SetInt("_DstAlpBlendMode", (int)BlendMode.OneMinusSrcAlpha);
break;
}
}
public enum UIShaderBlendMode
{
Background,
Others,
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a6dae782d369ff4d8daf5ef7ea96a6b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a6509bdf37b3b364eb80cb0df68435a3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5e0cbfbe15682c542acc5675d4503f72
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
// Copyright HTC Corporation All Rights Reserved.
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using VIVE.OpenXR.CompositionLayer;
namespace VIVE.OpenXR.Editor.CompositionLayer
{
[CustomEditor(typeof(ViveCompositionLayerExtraSettings))]
internal class ViveCompositionLayerEditorExtraSettings : UnityEditor.Editor
{
//private SerializedProperty SettingsEditorEnableSharpening;
static string PropertyName_SharpeningEnable = "SettingsEditorEnableSharpening";
static GUIContent Label_SharpeningEnable = new GUIContent("Enable Sharpening", "Enable Sharpening.");
SerializedProperty Property_SharpeningEnable;
static string PropertyName_SharpeningLevel = "SettingsEditorSharpeningLevel";
static GUIContent Label_SharpeningLevel = new GUIContent("Sharpening Level", "Select Sharpening Level.");
SerializedProperty Property_SharpeningLevel;
static string PropertyName_SharpeningMode = "SettingsEditorSharpeningMode";
static GUIContent Label_SharpeningMode = new GUIContent("Sharpening Mode", "Select Sharpening Mode.");
SerializedProperty Property_SharpeningMode;
void OnEnable()
{
Property_SharpeningEnable = serializedObject.FindProperty(PropertyName_SharpeningEnable);
Property_SharpeningMode = serializedObject.FindProperty(PropertyName_SharpeningMode);
Property_SharpeningLevel = serializedObject.FindProperty(PropertyName_SharpeningLevel);
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(Property_SharpeningEnable, new GUIContent(Label_SharpeningEnable));
EditorGUILayout.PropertyField(Property_SharpeningMode, new GUIContent(Label_SharpeningMode));
EditorGUILayout.PropertyField(Property_SharpeningLevel, new GUIContent(Label_SharpeningLevel));
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3dfbc6bb6d75454db700d2326157424
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 050772d662d04514ca3bb28fbe82ecd7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More