version 2.5.0

This commit is contained in:
Sean Lu
2025-01-10 17:17:03 +08:00
parent ddc3c4c6d8
commit 2372c9429a
1086 changed files with 290974 additions and 77367 deletions

View File

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

View File

@@ -0,0 +1,126 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Collections;
using UnityEngine;
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: AccessDebugTexture.cs
/// Role: General script
/// Responsibility: To assess the debug texture from SpectatorCameraBased.cs
/// </summary>
[RequireComponent(typeof(Renderer))]
public class AccessDebugTexture : MonoBehaviour
{
private static SpectatorCameraBased SpectatorCameraBased => SpectatorCameraBased.Instance;
// Some variables related to time definition for access SpectatorCameraBased class resources
private const float WaitSpectatorCameraBasedInitTime = 1.5f;
private const float WaitSpectatorCameraBasedPeriodTime = .5f;
private const float WaitSpectatorCameraBasedMaxTime = 10f;
private const int QuadrupleCheckIsRecordingCount = 4;
/// <summary>
/// The GameObject Renderer component
/// </summary>
private Renderer Renderer { get; set; }
/// <summary>
/// The default value of material in Renderer component
/// </summary>
private Material DefaultMaterial { get; set; }
/// <summary>
/// Set the Renderer material as debug material
/// </summary>
private void SetDebugMaterial()
{
Debug.Log("SetDebugMaterial");
if (SpectatorCameraBased)
{
if (SpectatorCameraBased.SpectatorCameraViewMaterial)
{
Renderer.material = SpectatorCameraBased.SpectatorCameraViewMaterial;
}
else
{
Debug.Log("No debug material set on SpectatorCameraBased.");
}
}
}
/// <summary>
/// Set the Renderer material as default material
/// </summary>
private void SetDefaultMaterial()
{
Debug.Log("SetDefaultMaterial");
Renderer.material = DefaultMaterial ? DefaultMaterial : null;
}
private IEnumerator Start()
{
float waitingTime = WaitSpectatorCameraBasedMaxTime;
bool getSpectatorCameraBased = false;
yield return new WaitForSeconds(WaitSpectatorCameraBasedInitTime);
do
{
if (!SpectatorCameraBased)
{
yield return new WaitForSeconds(WaitSpectatorCameraBasedPeriodTime);
waitingTime -= WaitSpectatorCameraBasedPeriodTime;
continue;
}
// Set -1 if accessed SpectatorCameraBased so we can break the while loop
waitingTime = -1;
getSpectatorCameraBased = true;
Renderer = GetComponent<Renderer>();
DefaultMaterial = Renderer.material;
SpectatorCameraBased.OnSpectatorStart += SetDebugMaterial;
SpectatorCameraBased.OnSpectatorStop += SetDefaultMaterial;
} while (waitingTime > 0);
if (!getSpectatorCameraBased)
{
Debug.Log($"Try to get SpectatorCameraBased " +
$"{WaitSpectatorCameraBasedMaxTime / WaitSpectatorCameraBasedPeriodTime} times but fail.");
Debug.Log("Destroy AccessDebugTexture now.");
Destroy(this);
yield break;
}
int quadrupleCheckCount = QuadrupleCheckIsRecordingCount;
while (quadrupleCheckCount > 0)
{
if (SpectatorCameraBased.IsRecording)
{
Debug.Log("Recording. Set debug material.");
SpectatorCameraBased.OnSpectatorStart?.Invoke();
break;
}
quadrupleCheckCount--;
yield return null;
Debug.Log("No recording. Keep default material.");
}
}
private void OnDestroy()
{
Renderer.material = DefaultMaterial ? DefaultMaterial : null;
if (SpectatorCameraBased)
{
SpectatorCameraBased.OnSpectatorStart -= SetDebugMaterial;
SpectatorCameraBased.OnSpectatorStop -= SetDefaultMaterial;
}
}
}
}

View File

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

View File

@@ -0,0 +1,146 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: SpectatorCameraBased.Editor.cs
/// Role: General script use in Unity Editor only
/// Responsibility: Display the SpectatorCameraBased.cs in Unity Inspector
/// </summary>
public partial class SpectatorCameraBased
{
#if UNITY_EDITOR
[SerializeField, Tooltip("State of debugging the spectator camera or not")]
private bool isDebugSpectatorCamera;
/// <summary>
/// State of debugging the spectator camera or not
/// </summary>
public bool IsDebugSpectatorCamera
{
get => isDebugSpectatorCamera;
set
{
isDebugSpectatorCamera = value;
if (!value)
{
IsRecording = false;
}
}
}
[CustomEditor(typeof(SpectatorCameraBased))]
public class SpectatorCameraBasedEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
// Just return if not "SpectatorCameraBased" class
if (!(target is SpectatorCameraBased))
{
return;
}
serializedObject.Update();
EditorGUI.BeginChangeCheck();
DrawGUI();
if (EditorGUI.EndChangeCheck())
{
Debug.Log("SpectatorCameraBased script is changed.");
EditorUtility.SetDirty(target);
}
serializedObject.ApplyModifiedProperties();
}
private void DrawGUI()
{
var script = (SpectatorCameraBased)target;
EditorGUI.BeginChangeCheck();
var currentSpectatorCameraViewMaterial = EditorGUILayout.ObjectField(
"Spectator Camera View Material",
script.SpectatorCameraViewMaterial,
typeof(Material),
false) as Material;
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, currentSpectatorCameraViewMaterial
? "Change Spectator Camera View Material"
: "Set Spectator Camera View Material as NULL");
script.SpectatorCameraViewMaterial = currentSpectatorCameraViewMaterial;
}
EditorGUI.BeginChangeCheck();
var currentIsDebugSpectatorCamera =
EditorGUILayout.Toggle("Active Spectator Camera Debugging", script.IsDebugSpectatorCamera);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Change IsDebugSpectatorCamera Value");
script.IsDebugSpectatorCamera = currentIsDebugSpectatorCamera;
}
if (script.IsDebugSpectatorCamera)
{
EditorGUI.BeginChangeCheck();
var currentIsRecording =
EditorGUILayout.Toggle("Active Spectator Camera Recording", script.IsRecording);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Change IsRecording Value");
script.IsRecording = currentIsRecording;
}
}
else
{
script.IsRecording = false;
}
if (script.IsDebugSpectatorCamera && GUILayout.Button("Load \"Simple_Demo_2\" scene for testing"))
{
if (DoesSceneExist("Simple_Demo_2"))
{
SceneManager.LoadScene("Simple_Demo_2");
}
else
{
Debug.LogWarning("Simple_Demo_2 scene not found. Please add it in build setting first.");
}
}
}
}
/// <summary>
/// Returns true if the scene 'name' exists and is in your Build settings, false otherwise.
/// </summary>
private static bool DoesSceneExist(string name)
{
if (string.IsNullOrEmpty(name))
{
return false;
}
for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
{
var scenePath = SceneUtility.GetScenePathByBuildIndex(i);
var lastSlash = scenePath.LastIndexOf("/", StringComparison.Ordinal);
var sceneName = scenePath.Substring(lastSlash + 1, scenePath.LastIndexOf(".", StringComparison.Ordinal) - lastSlash - 1);
if (string.Compare(name, sceneName, StringComparison.OrdinalIgnoreCase) == 0)
{
return true;
}
}
return false;
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,836 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
using VIVE.OpenXR.FirstPersonObserver;
using VIVE.OpenXR.SecondaryViewConfiguration;
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: SpectatorCameraBased.cs
/// Role: The base class cooperating with OpenXR SecondaryViewConfiguration Extension in Unity MonoBehaviour lifecycle (Singleton)
/// Responsibility: The handler responsible for the cooperation between the Unity MonoBehaviour lifecycle and OpenXR framework lifecycle
/// </summary>
public partial class SpectatorCameraBased : MonoBehaviour
{
private static SpectatorCameraBased _instance;
/// <summary>
/// SpectatorCameraBased static instance (Singleton)
/// </summary>
public static SpectatorCameraBased Instance => _instance;
#region Default value definition
/// <summary>
/// Camera texture width
/// </summary>
private const int TextureWidthDefault = 1920;
/// <summary>
/// Camera texture height
/// </summary>
private const int TextureHeightDefault = 1080;
/// <summary>
/// Camera GameObject Based Name
/// </summary>
private const string CameraGameObjectBasedName = "Spectator Camera Based Object";
/// <summary>
/// To define how long of the time (second) that the recording state is changed
/// </summary>
public const float RECORDING_STATE_CHANGE_THRESHOLD_IN_SECOND = 1f;
#endregion
#if !UNITY_EDITOR && UNITY_ANDROID
#region OpenXR Extension
/// <summary>
/// ViveFirstPersonObserver OpenXR extension
/// </summary>
private static ViveFirstPersonObserver FirstPersonObserver => ViveFirstPersonObserver.Instance;
/// <summary>
/// ViveSecondaryViewConfiguration OpenXR extension
/// </summary>
private static ViveSecondaryViewConfiguration SecondaryViewConfiguration => ViveSecondaryViewConfiguration.Instance;
#endregion
#region Locker and flag for multithread safety of texture updating
/// <summary>
/// Locker of NeedReInitTexture variables
/// </summary>
private readonly object _needReInitTextureLock = new object();
/// <summary>
/// State of whether re-init is needed for camera texture
/// </summary>
private bool NeedReInitTexture { get; set; }
/// <summary>
/// Locker of NeedUpdateTexture variables
/// </summary>
private readonly object _needUpdateTextureLock = new object();
/// <summary>
/// State of whether updated camera texture is needed
/// </summary>
private bool NeedUpdateTexture { get; set; }
#endregion
#endif
#region Spectator camera texture variables
/// <summary>
/// Camera texture size
/// </summary>
private Vector2 CameraTargetTextureSize { get; set; }
/// <summary>
/// Camera texture
/// </summary>
private RenderTexture CameraTargetTexture { get; set; }
#endregion
/// <summary>
/// GameObject of the spectator camera
/// </summary>
public GameObject SpectatorCameraGameObject { get; private set; }
/// <summary>
/// Camera component of the spectator camera
/// </summary>
public Camera SpectatorCamera { get; private set; }
public Camera MainCamera { get; private set; }
#region Debug Variables
[SerializeField] private Material spectatorCameraViewMaterial;
/// <summary>
/// Material that show the spectator camera view
/// </summary>
public Material SpectatorCameraViewMaterial
{
get => spectatorCameraViewMaterial;
set
{
spectatorCameraViewMaterial = value;
if (spectatorCameraViewMaterial && SpectatorCamera)
{
spectatorCameraViewMaterial.mainTexture = SpectatorCamera.targetTexture;
}
}
}
#endregion
private bool _followHmd;
/// <summary>
/// Is the spectator camera following the HMD or not
/// </summary>
public bool FollowHmd
{
get => _followHmd;
set
{
_followHmd = value;
if (SpectatorCamera.transform.parent != null &&
(SpectatorCamera.transform.localPosition != Vector3.zero ||
SpectatorCamera.transform.localRotation != Quaternion.identity))
{
Debug.Log("The local position or rotation should not be modified. Will reset the SpectatorCamera transform.");
SpectatorCamera.transform.localPosition = Vector3.zero;
SpectatorCamera.transform.localRotation = Quaternion.identity;
}
}
}
/// <summary>
/// State of allowing capture the 360 image or not
/// </summary>
public static bool IsAllowSpectatorCameraCapture360Image =>
#if !UNITY_EDITOR && UNITY_ANDROID
SecondaryViewConfiguration.IsAllowSpectatorCameraCapture360Image
#else
true
#endif
;
/// <summary>
/// SpectatorCameraBased init success or not
/// </summary>
private bool InitSuccess { get; set; }
/// <summary>
/// State of whether the app is not be focusing by the user
/// </summary>
private bool IsInBackground { get; set; }
[SerializeField, Tooltip("State of whether the spectator camera is recording currently")]
private bool isRecording;
/// <summary>
/// State of whether the spectator camera is recording currently
/// </summary>
public bool IsRecording
{
get => isRecording;
set
{
isRecording = value;
if (value)
{
if (IsPerformedStartRecordingCallback)
{
return;
}
IsPerformedStartRecordingCallback = true;
IsPerformedCloseRecordingCallback = false;
OnSpectatorStart?.Invoke();
}
else
{
if (IsPerformedCloseRecordingCallback ||
/* Because OpenXR periodically changes the spectator enabled flag, we need
to consider checking the state with a time delay so that we can make sure
it is changing for a long while or just periodically. */
Math.Abs(LastRecordingStateIsDisableTime - LastRecordingStateIsActiveTime) <
RECORDING_STATE_CHANGE_THRESHOLD_IN_SECOND)
{
return;
}
IsPerformedCloseRecordingCallback = true;
IsPerformedStartRecordingCallback = false;
OnSpectatorStop?.Invoke();
}
}
}
/// <summary>
/// The last time of the recording state that is active.
/// </summary>
public float LastRecordingStateIsActiveTime { get; private set; }
/// <summary>
/// The last time of the recording state that is disable.
/// </summary>
public float LastRecordingStateIsDisableTime { get; private set; }
/// <summary>
/// Flag denotes the callback is performed when the recording state changes to active
/// </summary>
private bool IsPerformedStartRecordingCallback { get; set; }
/// <summary>
/// Flag denotes the callback is performed when the recording state changes to disable
/// </summary>
private bool IsPerformedCloseRecordingCallback { get; set; }
#region Public variables for register the delegate callback functions
/// <summary>
/// Delegate type for spectator camera callbacks.
/// A delegate declaration that can encapsulate a method that takes no argument and returns void.
/// </summary>
public delegate void SpectatorCameraCallback();
/// <summary>
/// Delegate that custom code is executed when the spectator camera state changes to active.
/// </summary>
public SpectatorCameraCallback OnSpectatorStart;
/// <summary>
/// Delegate that custom code is executed when the spectator camera state changes to disable.
/// </summary>
public SpectatorCameraCallback OnSpectatorStop;
#endregion
#if !UNITY_EDITOR && UNITY_ANDROID
/// <summary>
/// Set the flag NeedReInitTexture as true
/// </summary>
/// <param name="size">The re-init texture size</param>
private void OnTextureSizeUpdated(Vector2 size)
{
lock (_needReInitTextureLock)
{
NeedReInitTexture = true;
CameraTargetTextureSize = size;
}
}
/// <summary>
/// Set the flag NeedUpdateTexture as true
/// </summary>
private void OnTextureUpdated()
{
lock (_needUpdateTextureLock)
{
NeedUpdateTexture = true;
}
}
/// <summary>
/// Init the projection matrix of spectator camera
/// </summary>
/// <param name="left">The position of the left vertical plane of the viewing frustum</param>
/// <param name="right">The position of the right vertical plane of the viewing frustum</param>
/// <param name="top">The position of the top horizontal plane of the viewing frustum</param>
/// <param name="bottom">The position of the bottom horizontal plane of the viewing frustum</param>
private void OnFovUpdated(float left, float right, float top, float bottom)
{
#region Modify the camera projection matrix (No need, just for reference)
/*
if (SpectatorCamera)
{
float far = SpectatorCamera.farClipPlane;
float near = SpectatorCamera.nearClipPlane;
SpectatorCamera.projectionMatrix = new Matrix4x4()
{
[0, 0] = 2f / (right - left),
[0, 1] = 0,
[0, 2] = (right + left) / (right - left),
[0, 3] = 0,
[1, 0] = 0,
[1, 1] = 2f / (top - bottom),
[1, 2] = (top + bottom) / (top - bottom),
[1, 3] = 0,
[2, 0] = 0,
[2, 1] = 0,
[2, 2] = -(far + near) / (far - near),
[2, 3] = -(2f * far * near) / (far - near),
[3, 0] = 0,
[3, 1] = 0,
[3, 2] = -1f,
[3, 3] = 0,
};
}
*/
#endregion
}
#endif
/// <summary>
/// Init the camera texture
/// </summary>
private void InitCameraTargetTexture()
{
if (CameraTargetTextureSize.x == 0 || CameraTargetTextureSize.y == 0)
{
#if !UNITY_EDITOR && UNITY_ANDROID
if (SecondaryViewConfiguration.TextureSize.x == 0 || SecondaryViewConfiguration.TextureSize.y == 0)
{
CameraTargetTextureSize = new Vector2(TextureWidthDefault, TextureHeightDefault);
}
else
{
CameraTargetTextureSize = SecondaryViewConfiguration.TextureSize;
}
#else
CameraTargetTextureSize = new Vector2(TextureWidthDefault, TextureHeightDefault);
#endif
}
if (!CameraTargetTexture)
{
// Texture is not create yet. Create it.
CameraTargetTexture = new RenderTexture
(
(int)CameraTargetTextureSize.x,
(int)CameraTargetTextureSize.y,
24,
RenderTextureFormat.ARGB32
);
InitPostProcessing();
return;
}
if (CameraTargetTexture.width == (int)CameraTargetTextureSize.x &&
CameraTargetTexture.height == (int)CameraTargetTextureSize.y)
{
// Texture size is same, just return.
return;
}
// Release the last time resource
SpectatorCamera.targetTexture = null;
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = null;
}
CameraTargetTexture.Release();
// Re-init
CameraTargetTexture.width = (int)CameraTargetTextureSize.x;
CameraTargetTexture.height = (int)CameraTargetTextureSize.y;
CameraTargetTexture.depth = 24;
CameraTargetTexture.format = RenderTextureFormat.ARGB32;
InitPostProcessing();
return;
void InitPostProcessing()
{
if (!CameraTargetTexture.IsCreated())
{
Debug.Log("The RenderTexture is not create yet. Will create it.");
bool created = CameraTargetTexture.Create();
Debug.Log($"Try to create RenderTexture: {created}");
if (created)
{
SpectatorCamera.targetTexture = CameraTargetTexture;
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = SpectatorCamera.targetTexture;
}
}
}
else
{
Debug.Log("The RenderTexture is already created.");
}
}
}
#if !UNITY_EDITOR && UNITY_ANDROID
/// <summary>
/// Update camera texture and then copy data of the camera texture to native texture space
/// </summary>
private void SecondViewTextureUpdate()
{
if (SecondaryViewConfiguration.MyTexture)
{
SpectatorCamera.enabled = true;
SpectatorCamera.Render();
SpectatorCamera.enabled = false;
if (SpectatorCamera.targetTexture)
{
// Copy Unity texture data to native texture
Graphics.CopyTexture(
SpectatorCamera.targetTexture,
0,
0,
SecondaryViewConfiguration.MyTexture,
0,
0);
}
else
{
Debug.LogError("Cannot copy the rendering data because the camera target texture is null!");
}
// Call native function that finishes the texture update
ViveSecondaryViewConfiguration.ReleaseSecondaryViewTexture();
}
else
{
Debug.LogError("Cannot copy the rendering data because SecondaryViewConfiguration.MyTexture is null!");
}
}
#endif
/// <summary>
/// Set the main texture of SpectatorCameraViewMaterial material as spectator camera texture
/// </summary>
private void SetCameraBasedTargetTexture2SpectatorCameraViewMaterial()
{
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = SpectatorCamera.targetTexture;
}
}
/// <summary>
/// Set the main texture of SpectatorCameraViewMaterial material as NULL value
/// </summary>
private void SetNull2SpectatorCameraViewMaterial()
{
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = null;
}
}
/// <summary>
/// Set whether the current camera viewpoint comes from HMD or not
/// </summary>
/// <param name="isViewFromHmd">The bool value represents the current view of whether the spectator camera is coming from hmd or not.</param>
public void SetViewFromHmd(bool isViewFromHmd)
{
#if !UNITY_EDITOR && UNITY_ANDROID
ViveSecondaryViewConfiguration.SetViewFromHmd(isViewFromHmd);
#endif
FollowHmd = isViewFromHmd;
}
/// <summary>
/// Get MainCamera in the current scene.
/// </summary>
/// <returns>The Camera component with MainCamera tag in the current scene</returns>
public static Camera GetMainCamera()
{
return Camera.main;
}
#region Unity life-cycle event
private void Start()
{
InitSuccess = false;
if (_instance != null && _instance != this)
{
Debug.Log("Destroy the SpectatorCameraBased");
if (SpectatorCameraViewMaterial)
{
Debug.Log("Copy SpectatorCameraBased material setting before destroy.");
_instance.SpectatorCameraViewMaterial = SpectatorCameraViewMaterial;
}
DestroyImmediate(this);
return;
}
else
{
_instance = this;
// To prevent this from being destroyed on load, check whether this gameObject has a parent;
// if so, set it to no game parent.
if (transform.parent != null)
{
transform.SetParent(null);
}
DontDestroyOnLoad(_instance.gameObject);
}
#if !UNITY_EDITOR && UNITY_ANDROID
if (SecondaryViewConfiguration && FirstPersonObserver)
{
// To check, "XR_MSFT_first_person_observer" is enough because it
// requires "XR_MSFT_secondary_view_configuration" to be enabled also.
if (!ViveFirstPersonObserver.IsExtensionEnabled())
{
Debug.LogWarning(
$"The OpenXR extension, {ViveSecondaryViewConfiguration.OPEN_XR_EXTENSION_STRING} " +
$"or {ViveFirstPersonObserver.OPEN_XR_EXTENSION_STRING}, is disabled. " +
"Please enable the extension before building the app.");
Debug.Log("Destroy the SpectatorCameraBased");
DestroyImmediate(this);
return;
}
SecondaryViewConfiguration.onTextureSizeUpdated += OnTextureSizeUpdated;
SecondaryViewConfiguration.onTextureUpdated += OnTextureUpdated;
SecondaryViewConfiguration.onFovUpdated += OnFovUpdated;
}
else
{
Debug.LogError(
"Cannot find the static instance of ViveSecondaryViewConfiguration or ViveFirstPersonObserver," +
" pls reopen the app later.");
Debug.Log("Destroy the SpectatorCameraBased");
DestroyImmediate(this);
return;
}
bool isSecondaryViewAlreadyEnabled = SecondaryViewConfiguration.IsEnabled;
Debug.Log(
$"The state of ViveSecondaryViewConfiguration.IsEnabled is {isSecondaryViewAlreadyEnabled}");
lock (_needReInitTextureLock)
{
NeedReInitTexture = isSecondaryViewAlreadyEnabled;
}
lock (_needUpdateTextureLock)
{
NeedUpdateTexture = isSecondaryViewAlreadyEnabled;
}
IsRecording = isSecondaryViewAlreadyEnabled;
#endif
SpectatorCameraGameObject = new GameObject(CameraGameObjectBasedName)
{
transform = { position = Vector3.zero, rotation = Quaternion.identity }
};
DontDestroyOnLoad(SpectatorCameraGameObject);
SpectatorCamera = SpectatorCameraGameObject.AddComponent<Camera>();
SpectatorCamera.stereoTargetEye = StereoTargetEyeMask.None;
MainCamera = GetMainCamera();
if (MainCamera != null)
{
// Set spectator camera to render after the main camera
SpectatorCamera.depth = MainCamera.depth + 1;
}
// Manually call Render() function once time at Start()
// because it can reduce the performance impact of first-time calls at SecondViewTextureUpdate
SpectatorCamera.Render();
SpectatorCamera.enabled = false;
FollowHmd = true;
IsInBackground = false;
IsPerformedStartRecordingCallback = false;
IsPerformedCloseRecordingCallback = false;
LastRecordingStateIsActiveTime = 0f;
LastRecordingStateIsDisableTime = 0f;
OnSpectatorStart += SetCameraBasedTargetTexture2SpectatorCameraViewMaterial;
OnSpectatorStop += SetNull2SpectatorCameraViewMaterial;
SceneManager.sceneLoaded += OnSceneLoaded;
#if !UNITY_EDITOR && UNITY_ANDROID
if (isSecondaryViewAlreadyEnabled)
{
OnSpectatorStart?.Invoke();
}
#endif
#if UNITY_EDITOR
OnSpectatorStart += () => { SpectatorCamera.enabled = true; };
OnSpectatorStop += () => { SpectatorCamera.enabled = false; };
CameraTargetTextureSize = new Vector2
(
TextureWidthDefault,
TextureHeightDefault
);
InitCameraTargetTexture();
SpectatorCamera.enabled = IsDebugSpectatorCamera && IsRecording;
#endif
InitSuccess = true;
}
private void LateUpdate()
{
if (!InitSuccess)
{
return;
}
if (IsInBackground)
{
return;
}
if (SpectatorCamera.transform.parent != null &&
(SpectatorCamera.transform.localPosition != Vector3.zero ||
SpectatorCamera.transform.localRotation != Quaternion.identity))
{
Debug.Log("The local position or rotation should not be modified. Will reset the SpectatorCamera transform.");
SpectatorCamera.transform.localPosition = Vector3.zero;
SpectatorCamera.transform.localRotation = Quaternion.identity;
}
if (FollowHmd)
{
if (MainCamera != null || (MainCamera = GetMainCamera()) != null)
{
Transform spectatorCameraTransform = SpectatorCamera.transform;
Transform hmdCameraTransform = MainCamera.transform;
spectatorCameraTransform.position = hmdCameraTransform.position;
spectatorCameraTransform.rotation = hmdCameraTransform.rotation;
}
}
else
{
#if !UNITY_EDITOR && UNITY_ANDROID
if (!SecondaryViewConfiguration.IsStopped)
{
Transform referenceTransform = SpectatorCamera.transform;
// Left-handed coordinate system (Unity) -> right-handed coordinate system (OpenXR)
var spectatorCameraPositionInOpenXRSpace = new XrVector3f
(
referenceTransform.position.x,
referenceTransform.position.y,
-referenceTransform.position.z
);
var spectatorCameraQuaternionInOpenXRSpace = new XrQuaternionf
(
referenceTransform.rotation.x,
referenceTransform.rotation.y,
-referenceTransform.rotation.z,
-referenceTransform.rotation.w
);
var spectatorCameraPose = new XrPosef
(
spectatorCameraQuaternionInOpenXRSpace,
spectatorCameraPositionInOpenXRSpace
);
ViveSecondaryViewConfiguration.SetNonHmdViewPose(spectatorCameraPose);
}
#endif
}
#if !UNITY_EDITOR && UNITY_ANDROID
IsRecording = SecondaryViewConfiguration.IsEnabled;
#endif
if (IsRecording)
{
LastRecordingStateIsActiveTime = Time.unscaledTime;
}
else
{
LastRecordingStateIsDisableTime = Time.unscaledTime;
if (!IsPerformedCloseRecordingCallback &&
/* Because OpenXR periodically changes the spectator enabled flag, we need
to consider checking the state with a time delay so that we can make sure
it is changing for a long while or just periodically. */
Math.Abs(LastRecordingStateIsDisableTime - LastRecordingStateIsActiveTime) >
RECORDING_STATE_CHANGE_THRESHOLD_IN_SECOND)
{
IsPerformedCloseRecordingCallback = true;
IsPerformedStartRecordingCallback = false;
OnSpectatorStop?.Invoke();
}
return;
}
#if !UNITY_EDITOR && UNITY_ANDROID
lock (_needReInitTextureLock)
{
if (NeedReInitTexture)
{
NeedReInitTexture = false;
InitCameraTargetTexture();
}
}
lock (_needUpdateTextureLock)
{
if (NeedUpdateTexture)
{
NeedUpdateTexture = false;
ViveSecondaryViewConfiguration.SetStateSecondaryViewImageDataReady(false);
SecondViewTextureUpdate();
ViveSecondaryViewConfiguration.SetStateSecondaryViewImageDataReady(true);
}
}
#endif
}
private void OnApplicationFocus(bool hasFocus)
{
if (!InitSuccess)
{
Debug.Log("Init unsuccessfully, just return from SpectatorCameraBased.OnApplicationFocus.");
return;
}
Debug.Log($"SpectatorCameraBased.OnApplicationFocus: {hasFocus}");
}
private void OnApplicationPause(bool pauseStatus)
{
if (!InitSuccess)
{
Debug.Log("Init unsuccessfully, just return from SpectatorCameraBased.OnApplicationPause.");
return;
}
Debug.Log($"SpectatorCameraBased.OnApplicationPause: {pauseStatus}");
#if !UNITY_EDITOR && UNITY_ANDROID
// Need to re-create the swapchain when recording is active and Unity app is resumed
if (SecondaryViewConfiguration.IsEnabled && !pauseStatus)
{
ViveSecondaryViewConfiguration.RequireReinitSwapchain();
}
#endif
IsInBackground = pauseStatus;
}
private void OnDestroy()
{
if (!InitSuccess)
{
Debug.Log("Init unsuccessfully, just return from SpectatorCameraBased.OnDestroy.");
return;
}
Debug.Log("SpectatorCameraBased.OnDestroy");
#if !UNITY_EDITOR && UNITY_ANDROID
SecondaryViewConfiguration.onTextureSizeUpdated -= OnTextureSizeUpdated;
SecondaryViewConfiguration.onTextureUpdated -= OnTextureUpdated;
#endif
if (SpectatorCamera)
{
SpectatorCamera.targetTexture = null;
}
if (SpectatorCameraViewMaterial)
{
SpectatorCameraViewMaterial.mainTexture = null;
}
if (CameraTargetTexture)
{
Destroy(CameraTargetTexture);
}
#if !UNITY_EDITOR && UNITY_ANDROID
ViveSecondaryViewConfiguration.ReleaseAllResources();
#endif
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (!InitSuccess)
{
Debug.Log("Init unsuccessfully, just return from SpectatorCameraBased.OnSceneLoaded.");
return;
}
Debug.Log($"SpectatorCameraBased.OnSceneLoaded: {scene.name}");
MainCamera = GetMainCamera();
#if !UNITY_EDITOR && UNITY_ANDROID
if (!SecondaryViewConfiguration.IsStopped)
{
// Need to re-init the swapchain when recording is active and new Unity scene is loaded
ViveSecondaryViewConfiguration.RequireReinitSwapchain();
}
#endif
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,93 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: SecondaryViewConfiguration.Editor.cs
/// Role: General script use in Unity Editor only
/// Responsibility: Display the SecondaryViewConfiguration.cs in Unity Project Settings
/// </summary>
public partial class ViveSecondaryViewConfiguration
{
[field: SerializeField] internal bool IsAllowSpectatorCameraCapture360Image { get; set; }
[field: SerializeField] internal bool IsEnableDebugLog { get; set; }
#if UNITY_EDITOR
[CustomEditor(typeof(ViveSecondaryViewConfiguration))]
public class ViveSecondaryViewConfigurationEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
// Just return if not "ViveSecondaryViewConfiguration" class
if (!(target is ViveSecondaryViewConfiguration))
{
return;
}
serializedObject.Update();
DrawGUI();
serializedObject.ApplyModifiedProperties();
}
private void DrawGUI()
{
var script = (ViveSecondaryViewConfiguration)target;
EditorGUI.BeginChangeCheck();
var currentIsAllowSpectatorCameraCapture360Image =
EditorGUILayout.Toggle("Allow capture panorama", script.IsAllowSpectatorCameraCapture360Image);
if (EditorGUI.EndChangeCheck())
{
if (currentIsAllowSpectatorCameraCapture360Image && !PlayerSettings.enable360StereoCapture)
{
const string acceptButtonString =
"OK";
const string cancelButtonString =
"Cancel";
const string openCapture360ImageAdditionRequestTitle =
"Additional Request of Capturing 360 Image throughout the Spectator Camera";
const string openCapture360ImageAdditionRequestDescription =
"Allow the spectator camera to capture 360 images. Addition Request:\n" +
"1.) Open the \"enable360StereoCapture\" in the Unity Player Setting " +
"Page.";
bool acceptDialog1 = EditorUtility.DisplayDialog(
openCapture360ImageAdditionRequestTitle,
openCapture360ImageAdditionRequestDescription,
acceptButtonString,
cancelButtonString);
if (acceptDialog1)
{
PlayerSettings.enable360StereoCapture = true;
}
else
{
return;
}
}
Undo.RecordObject(target, "Modified ViveSecondaryViewConfiguration IsAllowSpectatorCameraCapture360Image");
EditorUtility.SetDirty(target);
script.IsAllowSpectatorCameraCapture360Image = currentIsAllowSpectatorCameraCapture360Image;
}
EditorGUI.BeginChangeCheck();
var currentIsEnableDebugLog =
EditorGUILayout.Toggle("Print log for debugging", script.IsEnableDebugLog);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Modified ViveSecondaryViewConfiguration IsEnableDebugLog");
EditorUtility.SetDirty(target);
script.IsEnableDebugLog = currentIsEnableDebugLog;
}
}
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,898 @@
// Copyright HTC Corporation All Rights Reserved.
using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.SecondaryViewConfiguration
{
/// <summary>
/// Name: SecondaryViewConfiguration.cs
/// Role: OpenXR SecondaryViewConfiguration Extension Class
/// Responsibility: The OpenXR extension implementation and its lifecycles logic in OpenXR
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Spectator Camera (Beta)",
BuildTargetGroups = new[] { BuildTargetGroup.Android },
Company = "HTC",
Desc = "Allows an application to enable support for one or more secondary view configurations.",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = OPEN_XR_EXTENSION_STRING,
Version = "1.0.0",
FeatureId = FeatureId)]
#endif
public partial class ViveSecondaryViewConfiguration : OpenXRFeature
{
#region Varibles
private static ViveSecondaryViewConfiguration _instance;
/// <summary>
/// ViveSecondaryViewConfiguration static instance (Singleton).
/// </summary>
public static ViveSecondaryViewConfiguration Instance
{
get
{
if (_instance == null)
{
_instance =
OpenXRSettings.Instance.GetFeature<ViveSecondaryViewConfiguration>();
}
return _instance;
}
}
#region OpenXR variables related to definition
/// <summary>
/// The log identification.
/// </summary>
private const string LogTag = "VIVE.OpenXR.SecondaryViewConfiguration";
/// <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.secondaryviewconfiguration";
/// <summary>
/// OpenXR specification <a href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_MSFT_secondary_view_configuration">12.122. XR_MSFT_secondary_view_configuration</a>.
/// </summary>
public const string OPEN_XR_EXTENSION_STRING = "XR_MSFT_secondary_view_configuration";
/// <summary>
/// The extension library name.
/// </summary>
private const string ExtLib = "libviveopenxr";
#endregion
#region OpenXR variables related to its life-cycle
/// <summary>
/// The flag represents whether the OpenXR loader created an instance or not.
/// </summary>
private bool XrInstanceCreated { get; set; } = false;
/// <summary>
/// The flag represents whether the OpenXR loader created a session or not.
/// </summary>
private bool XrSessionCreated { get; set; } = false;
/// <summary>
/// The flag represents whether the OpenXR loader started a session or not.
/// </summary>
private bool XrSessionStarted { get; set; } = false;
/// <summary>
/// The instance created through xrCreateInstance.
/// </summary>
private XrInstance XrInstance { get; set; } = 0;
/// <summary>
/// An XrSystemId is an opaque atom used by the runtime to identify a system.
/// </summary>
private XrSystemId XrSystemId { get; set; } = 0;
/// <summary>
/// A session represents an applications intention to display XR content to the user.
/// </summary>
private XrSession XrSession { get; set; } = 0;
/// <summary>
/// New possible session lifecycle states.
/// </summary>
private XrSessionState XrSessionNewState { get; set; } = XrSessionState.XR_SESSION_STATE_UNKNOWN;
/// <summary>
/// The previous state possible session lifecycle states.
/// </summary>
private XrSessionState XrSessionOldState { get; set; } = XrSessionState.XR_SESSION_STATE_UNKNOWN;
/// <summary>
/// The function delegate declaration of xrGetInstanceProcAddr.
/// </summary>
private OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr { get; set; }
#endregion
#region Variables related to handle agent functions
/// <summary>
/// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod".
/// </summary>
private delegate void SetSecondaryViewConfigurationStateDelegate(bool isEnabled);
/// <summary>
/// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod".
/// </summary>
private delegate void StopEnableSecondaryViewConfigurationDelegate(bool isStopped);
/// <summary>
/// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod".
/// </summary>
private delegate void SetTextureSizeDelegate(UInt32 width, UInt32 height);
/// <summary>
/// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod".
/// </summary>
private delegate void SetFovDelegate(XrFovf fov);
#endregion
#region Variables related to callback functions instantiation
/// <summary>
/// A delegate declaration can encapsulate the method that takes a Vector2 argument and returns void.
/// </summary>
public delegate void OnTextureSizeUpdatedDelegate(Vector2 size);
/// <summary>
/// The instantiation of the delegate OnTextureSizeUpdatedDelegate. This will be called when the texture size coming from the native plugin is updated.
/// </summary>
public OnTextureSizeUpdatedDelegate onTextureSizeUpdated;
/// <summary>
/// A delegate declaration can encapsulate the method that takes no argument and returns void.
/// </summary>
public delegate void OnTextureUpdatedDelegate();
/// <summary>
/// The instantiation of the delegate OnTextureUpdatedDelegate. This will be called when the texture coming from the native plugin is updated.
/// </summary>
public OnTextureUpdatedDelegate onTextureUpdated;
/// <summary>
/// A delegate declaration can encapsulate the method that takes four floating-point arguments, left, right, up, and down, respectively, and returns void.
/// </summary>
public delegate void OnFovUpdatedDelegate(float left, float right, float up, float down);
/// <summary>
/// The instantiation of the delegate OnFovUpdatedDelegate. This will be called when the fov setting coming from the native plugin is updated.
/// </summary>
public OnFovUpdatedDelegate onFovUpdated;
#endregion
#region Rendering and texture varibles
/// <summary>
/// The graphics backend that the current application is used.
/// </summary>
private static GraphicsAPI MyGraphicsAPI
{
get
{
return SystemInfo.graphicsDeviceType switch
{
GraphicsDeviceType.OpenGLES3 => GraphicsAPI.GLES3,
GraphicsDeviceType.Vulkan => GraphicsAPI.Vulkan,
_ => GraphicsAPI.Unknown
};
}
}
private Vector2 _textureSize;
/// <summary>
/// The value of texture size coming from the native plugin.
/// </summary>
public Vector2 TextureSize
{
get => _textureSize;
private set
{
_textureSize = value;
onTextureSizeUpdated?.Invoke(value);
}
}
private Texture _myTexture;
/// <summary>
/// The texture handler adopts the native 2D texture object coming from the native plugin.
/// </summary>
public Texture MyTexture
{
get => _myTexture;
private set
{
_myTexture = value;
onTextureUpdated?.Invoke();
}
}
#endregion
#region The variables (flag) represent the state related to this OpenXR extension
/// <summary>
/// The state of the second view configuration comes from runtime.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// The flag represents to whether the second view configuration is disabled or not.
/// </summary>
public bool IsStopped { get; set; }
/// <summary>
/// The flag represents to whether the "SpectatorCameraBased" script exists in the Unity scene.
/// </summary>
private bool IsExistSpectatorCameraBased { get; set; }
#endregion
#endregion
#region Function
#region OpenXR life-cycle functions
/// <summary>
/// Called after xrCreateInstance.
/// </summary>
/// <param name="xrInstance">Handle of the xrInstance.</param>
/// <returns>Returns true if successful. Returns false otherwise.</returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!IsExtensionEnabled())
{
Warning("OnInstanceCreate() " + OPEN_XR_EXTENSION_STRING + " is NOT enabled.");
return false;
}
var mySpectatorCameraBasedGameObject = GetSpectatorCameraBased();
IsExistSpectatorCameraBased = mySpectatorCameraBasedGameObject != null;
XrInstanceCreated = true;
XrInstance = xrInstance;
Debug("OnInstanceCreate() " + XrInstance);
if (!GetXrFunctionDelegates(XrInstance))
{
Error("Get function pointer of OpenXRFunctionPointerAccessor failed.");
return false;
}
Debug("Get function pointer of OpenXRFunctionPointerAccessor succeed.");
return base.OnInstanceCreate(xrInstance);
}
/// <summary>
/// Called after xrGetSystem
/// </summary>
/// <param name="xrSystem">Handle of the xrSystemId</param>
protected override void OnSystemChange(ulong xrSystem)
{
XrSystemId = xrSystem;
Debug("OnSystemChange() " + XrSystemId);
base.OnSystemChange(xrSystem);
}
/// <summary>
/// Called after xrCreateSession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionCreate(ulong xrSession)
{
XrSessionCreated = true;
XrSession = xrSession;
Debug("OnSessionCreate() " + XrSession);
base.OnSessionCreate(xrSession);
}
/// <summary>
/// Called after xrSessionBegin.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionBegin(ulong xrSession)
{
XrSessionStarted = true;
Debug("OnSessionBegin() " + XrSessionStarted);
base.OnSessionBegin(xrSession);
}
/// <summary>
/// Called when the OpenXR loader receives the XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED event from the runtime signaling that the XrSessionState has changed.
/// </summary>
/// <param name="oldState">Previous state.</param>
/// <param name="newState">New state.</param>
protected override void OnSessionStateChange(int oldState, int newState)
{
Debug("OnSessionStateChange() oldState: " + oldState + " newState:" + newState);
if (Enum.IsDefined(typeof(XrSessionState), oldState))
{
XrSessionOldState = (XrSessionState)oldState;
}
else
{
Warning("OnSessionStateChange() oldState undefined");
}
if (Enum.IsDefined(typeof(XrSessionState), newState))
{
XrSessionNewState = (XrSessionState)newState;
}
else
{
Warning("OnSessionStateChange() newState undefined");
}
base.OnSessionStateChange(oldState, newState);
}
/// <summary>
/// Called to hook xrGetInstanceProcAddr. Returning a different function pointer allows intercepting any OpenXR method.
/// </summary>
/// <param name="func">xrGetInstanceProcAddr native function pointer.</param>
/// <returns>Function pointer that Unity will use to look up OpenXR native functions.</returns>
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
Debug("HookGetInstanceProcAddr Start");
if (MyGraphicsAPI is GraphicsAPI.GLES3 or GraphicsAPI.Vulkan)
{
Debug($"The app graphics API is {MyGraphicsAPI}");
return SecondaryViewConfigurationInterceptOpenXRMethod
(
MyGraphicsAPI,
SystemInfo.graphicsUVStartsAtTop,
func,
SetSecondaryViewConfigurationState,
StopEnableSecondaryViewConfiguration,
SetTextureSize,
SetFov
);
}
Error(
"The render backend is not supported. Requires OpenGL or Vulkan backend for secondary view configuration feature.");
return base.HookGetInstanceProcAddr(func);
}
/// <summary>
/// Called before xrEndSession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionEnd(ulong xrSession)
{
XrSessionStarted = false;
Debug("OnSessionEnd() " + XrSession);
base.OnSessionEnd(xrSession);
}
/// <summary>
/// Called before xrDestroySession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
XrSessionCreated = false;
Debug("OnSessionDestroy() " + xrSession);
base.OnSessionDestroy(xrSession);
}
/// <summary>
/// Called before xrDestroyInstance.
/// </summary>
/// <param name="xrInstance">Handle of the xrInstance.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
XrInstanceCreated = false;
XrInstance = 0;
Debug("OnInstanceDestroy() " + xrInstance);
base.OnInstanceDestroy(xrInstance);
}
#endregion
#region Handle agent functions
/// <summary>
/// This function is defined as the "SetSecondaryViewConfigurationStateDelegate" delegate function.
/// <b>Please be careful that this function should ONLY be called by native plug-ins.
/// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE.</b>
/// </summary>
/// <param name="isEnabled">The state of the second view configuration comes from runtime. True if enabled. False otherwise.</param>
[MonoPInvokeCallback(typeof(SetSecondaryViewConfigurationStateDelegate))]
private static void SetSecondaryViewConfigurationState(bool isEnabled)
{
Instance.IsEnabled = isEnabled;
if (Instance.IsEnableDebugLog)
{
Debug($"SetSecondaryViewConfigurationState: Instance.IsEnabled set as {Instance.IsEnabled}");
}
}
/// <summary>
/// This function is defined as the "StopEnableSecondaryViewConfigurationDelegate" delegate function.
/// <b>Please be careful that this function should ONLY be called by native plug-ins.
/// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE.</b>
/// </summary>
/// <param name="isStopped">The flag refers to whether the second view configuration is disabled or not. True if the second view configuration is disabled. False otherwise.</param>
[MonoPInvokeCallback(typeof(StopEnableSecondaryViewConfigurationDelegate))]
private static void StopEnableSecondaryViewConfiguration(bool isStopped)
{
Instance.IsStopped = isStopped;
if (Instance.IsEnableDebugLog)
{
Debug($"StopEnableSecondaryViewConfiguration: Instance.IsStopped set as {Instance.IsStopped}");
}
}
/// <summary>
/// This function is defined as the "SetTextureSizeDelegate" delegate function.
/// <b>Please be careful that this function should ONLY be called by native plug-ins.
/// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE.</b>
/// </summary>
/// <param name="width">The texture width comes from runtime.</param>
/// <param name="height">The texture height comes from runtime.</param>
[MonoPInvokeCallback(typeof(SetTextureSizeDelegate))]
private static void SetTextureSize(uint width, uint height)
{
if (!Instance.IsExistSpectatorCameraBased)
{
CreateSpectatorCameraBased();
}
Instance.TextureSize = new Vector2(width, height);
if (Instance.IsEnableDebugLog)
{
Debug($"SetTextureSize width: {Instance.TextureSize.x}, height: {Instance.TextureSize.y}");
}
IntPtr texPtr = GetSecondaryViewTextureId(out uint imageIndex);
if (Instance.IsEnableDebugLog)
{
Debug($"SetTextureSize texPtr: {texPtr}, imageIndex: {imageIndex}");
}
if (texPtr == IntPtr.Zero)
{
Error($"SetTextureSize texPtr is invalid: {texPtr}");
return;
}
if (Instance.IsEnableDebugLog)
{
Debug("Get ptr successfully");
}
Instance.MyTexture = Texture2D.CreateExternalTexture(
(int)Instance.TextureSize.x,
(int)Instance.TextureSize.y,
TextureFormat.RGBA32,
false,
false,
texPtr);
#region For development usage (Just for reference)
/*
if (Instance.IsEnableDebugLog)
{
Debug("Create texture successfully");
Debug($"Instance.MyTexture.height: {Instance.MyTexture.height}");
Debug($"Instance.MyTexture.width: {Instance.MyTexture.width}");
Debug($"Instance.MyTexture.dimension: {Instance.MyTexture.dimension}");
Debug($"Instance.MyTexture.anisoLevel: {Instance.MyTexture.anisoLevel}");
Debug($"Instance.MyTexture.filterMode: {Instance.MyTexture.filterMode}");
Debug($"Instance.MyTexture.wrapMode: {Instance.MyTexture.wrapMode}");
Debug($"Instance.MyTexture.graphicsFormat: {Instance.MyTexture.graphicsFormat}");
Debug($"Instance.MyTexture.isReadable: {Instance.MyTexture.isReadable}");
Debug($"Instance.MyTexture.texelSize: {Instance.MyTexture.texelSize}");
Debug($"Instance.MyTexture.mipmapCount: {Instance.MyTexture.mipmapCount}");
Debug($"Instance.MyTexture.updateCount: {Instance.MyTexture.updateCount}");
Debug($"Instance.MyTexture.mipMapBias: {Instance.MyTexture.mipMapBias}");
Debug($"Instance.MyTexture.wrapModeU: {Instance.MyTexture.wrapModeU}");
Debug($"Instance.MyTexture.wrapModeV: {Instance.MyTexture.wrapModeV}");
Debug($"Instance.MyTexture.wrapModeW: {Instance.MyTexture.wrapModeW}");
Debug($"Instance.MyTexture.filterMode: {Instance.MyTexture.name}");
Debug($"Instance.MyTexture.hideFlags: {Instance.MyTexture.hideFlags}");
Debug($"Instance.MyTexture.GetInstanceID(): {Instance.MyTexture.GetInstanceID()}");
Debug($"Instance.MyTexture.GetType(): {Instance.MyTexture.GetType()}");
Debug($"Instance.MyTexture.GetNativeTexturePtr(): {Instance.MyTexture.GetNativeTexturePtr()}");
// Print imageContentsHash will cause an error
// Debug($"Instance.MyTexture.imageContentsHash: {Instance.MyTexture.imageContentsHash}");
}
*/
#endregion
}
/// <summary>
/// This function is defined as the "SetFovDelegate" delegate function.
/// <b>Please be careful that this function should ONLY be called by native plug-ins.
/// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE.</b>
/// </summary>
/// <param name="fov">The fov value comes from runtime.</param>
[MonoPInvokeCallback(typeof(SetFovDelegate))]
private static void SetFov(XrFovf fov)
{
if (Instance.IsEnableDebugLog)
{
Debug($"fov.AngleDown {fov.angleDown}");
Debug($"fov.AngleLeft {fov.angleLeft}");
Debug($"fov.AngleRight {fov.angleRight}");
Debug($"fov.AngleUp {fov.angleUp}");
}
Instance.onFovUpdated?.Invoke(fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown);
}
#endregion
#region C++ interop functions
/// <summary>
/// Call this function to trigger the native plug-in that gets a specific OpenXR function for services to the
/// Unity engine, such as dispatching the Unity data to runtime and returning the data from runtime to the
/// Unity engine.
/// </summary>
/// <param name="xrInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <param name="xrGetInstanceProcAddrFuncPtr">Accessor for xrGetInstanceProcAddr function pointer.</param>
/// <returns>Return true if get successfully. False otherwise.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "secondary_view_configuration_get_function_address")]
private static extern bool SecondaryViewConfigurationGetFunctionAddress
(
XrInstance xrInstance,
IntPtr xrGetInstanceProcAddrFuncPtr
);
/// <summary>
/// Call this function to dispatch/hook all OpenXR functions to native plug-ins.
/// </summary>
/// <param name="graphicsAPI">The graphics backend adopted in the Unity engine.</param>
/// <param name="graphicsUVStartsAtTop">The bool value represents whether the texture UV coordinate convention for this platform has Y starting at the top of the image.</param>
/// <param name="func">xrGetInstanceProcAddr native function pointer.</param>
/// <param name="setSecondaryViewConfigurationStateDelegate">The delegate function pointer that functions types as "SetSecondaryViewConfigurationStateDelegate".</param>
/// <param name="stopEnableSecondaryViewConfigurationDelegate">The delegate function pointer that functions types as "StopEnableSecondaryViewConfigurationDelegate".</param>
/// <param name="setTextureSizeDelegate">The delegate function pointer that functions types as "SetTextureSizeDelegate".</param>
/// <param name="setFovDelegate">The delegate function pointer that functions types as "SetFovDelegate".</param>
/// <returns></returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "secondary_view_configuration_intercept_openxr_method")]
private static extern IntPtr SecondaryViewConfigurationInterceptOpenXRMethod
(
GraphicsAPI graphicsAPI,
bool graphicsUVStartsAtTop,
IntPtr func,
[MarshalAs(UnmanagedType.FunctionPtr)]
SetSecondaryViewConfigurationStateDelegate setSecondaryViewConfigurationStateDelegate,
[MarshalAs(UnmanagedType.FunctionPtr)]
StopEnableSecondaryViewConfigurationDelegate stopEnableSecondaryViewConfigurationDelegate,
[MarshalAs(UnmanagedType.FunctionPtr)] SetTextureSizeDelegate setTextureSizeDelegate,
[MarshalAs(UnmanagedType.FunctionPtr)] SetFovDelegate setFovDelegate
);
/// <summary>
/// Call this function to get the current swapchain image handler (its ID and memory address).
/// </summary>
/// <param name="imageIndex">The current handler index.</param>
/// <returns>The current handler memory address.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "get_secondary_view_texture_id")]
private static extern IntPtr GetSecondaryViewTextureId
(
out UInt32 imageIndex
);
/// <summary>
/// Call this function to tell native plug-in submit the swapchain image immediately.
/// </summary>
/// <returns>Return true if submit the swapchain image successfully. False otherwise.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "release_secondary_view_texture")]
public static extern bool ReleaseSecondaryViewTexture();
/// <summary>
/// Call this function to release all resources in native plug-in. Please be careful that this function should
/// ONLY call in the Unity "OnDestroy" lifecycle event in the class "SpectatorCameraBased".
/// </summary>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "release_all_resources")]
public static extern void ReleaseAllResources();
/// <summary>
/// Call this function if requiring swapchain re-initialization. The native plug-in will set a re-initialization
/// flag. Once the secondary view is enabled after that, the swapchain will re-init immediately.
/// </summary>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "require_reinit_swapchain")]
public static extern void RequireReinitSwapchain();
/// <summary>
/// Call this function to tell the native plug-in where the current spectator camera source comes from.
/// </summary>
/// <param name="isViewFromHmd">Please set true if the source comes from hmd. Otherwise, please set false.</param>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "set_view_from_hmd")]
public static extern void SetViewFromHmd(bool isViewFromHmd);
/// <summary>
/// Call this function to tell the non-hmd pose to the native plug-in.
/// </summary>
/// <param name="pose">The current non-hmd pose</param>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "set_non_hmd_view_pose")]
public static extern void SetNonHmdViewPose(XrPosef pose);
/// <summary>
/// Call this function to tell the native plug-in whether the texture data is ready or not.
/// </summary>
/// <param name="isReady">The texture data written by Unity Engine is ready or not.</param>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "set_state_secondary_view_image_data_ready")]
public static extern void SetStateSecondaryViewImageDataReady(bool isReady);
#endregion
#region Utilities functions
/// <summary>
/// Check ViveSecondaryViewConfiguration extension is enabled or not.
/// </summary>
/// <returns>Return true if enabled. False otherwise.</returns>
public static bool IsExtensionEnabled()
{
#if UNITY_2022_1_OR_NEWER
return OpenXRRuntime.IsExtensionEnabled(OPEN_XR_EXTENSION_STRING);
#else
// Does not support 2021 or lower
return false;
#endif
}
/// <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 get successfully. False otherwise.</returns>
private bool GetXrFunctionDelegates(XrInstance xrInstance)
{
if (xrGetInstanceProcAddr != IntPtr.Zero)
{
Debug("Get function pointer of openXRFunctionPointerAccessor.");
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(xrGetInstanceProcAddr,
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
if (XrGetInstanceProcAddr == null)
{
Error(
"Get function pointer of openXRFunctionPointerAccessor failed due to the XrGetInstanceProcAddr is null.");
return false;
}
}
else
{
Error(
"Get function pointer of openXRFunctionPointerAccessor failed due to the xrGetInstanceProcAddr is null.");
return false;
}
Debug("Try to get the function pointer for XR_MSFT_secondary_view_configuration.");
return SecondaryViewConfigurationGetFunctionAddress(xrInstance, xrGetInstanceProcAddr);
#region Get function in C# (Just for reference)
/* if (GetOpenXRDelegateFunction(
XrGetInstanceProcAddr,
xrInstance,
"xrEnumerateViewConfigurations",
out _xrEnumerateViewConfigurations) is false)
{
Error("Get delegate function of XrEnumerateViewConfigurations failed.");
return false;
} */
#endregion
}
/// <summary>
/// Get the specific OpenXR function.
/// </summary>
/// <param name="openXRFunctionPointerAccessor">The function pointer accessor provide by OpenXR.</param>
/// <param name="openXRInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <param name="functionName">The specific OpenXR function.</param>
/// <param name="delegateFunction">Override value. The specific OpenXR function.</param>
/// <typeparam name="T">The class of the delegate function.</typeparam>
/// <returns>Return true if get successfully. False otherwise.</returns>
private static bool GetOpenXRDelegateFunction<T>
(
in OpenXRHelper.xrGetInstanceProcAddrDelegate openXRFunctionPointerAccessor,
in XrInstance openXRInstance,
in string functionName,
out T delegateFunction
) where T : class
{
delegateFunction = default(T);
if (openXRFunctionPointerAccessor == null || openXRInstance == 0 || string.IsNullOrEmpty(functionName))
{
Error($"Get OpenXR delegate function, {functionName}, failed due to the invalid parameter(s).");
return false;
}
XrResult getFunctionState = openXRFunctionPointerAccessor(openXRInstance, functionName, out IntPtr funcPtr);
bool funcPtrIsNull = funcPtr == IntPtr.Zero;
Debug("Get OpenXR delegate function, " + functionName + ", state: " + getFunctionState);
Debug("Get OpenXR delegate function, " + functionName + ", funcPtrIsNull: " + funcPtrIsNull);
if (getFunctionState != XrResult.XR_SUCCESS || funcPtrIsNull)
{
Error(
$"Get OpenXR delegate function, {functionName}, failed due to the native error or invalid return.");
return false;
}
try
{
delegateFunction = Marshal.GetDelegateForFunctionPointer(funcPtr, typeof(T)) as T;
}
catch (Exception e)
{
Error($"Get OpenXR delegate function, {functionName}, failed due to the exception: {e.Message}");
return false;
}
Debug($"Get OpenXR delegate function, {functionName}, succeed.");
return true;
}
/// <summary>
/// Print log with tag "VIVE.OpenXR.SecondaryViewConfiguration".
/// </summary>
/// <param name="msg">The log you want to print.</param>
private static void Debug(string msg)
{
UnityEngine.Debug.Log(LogTag + " " + msg);
}
/// <summary>
/// Print warning message with tag "VIVE.OpenXR.SecondaryViewConfiguration".
/// </summary>
/// <param name="msg">The warning message you want to print.</param>
private static void Warning(string msg)
{
UnityEngine.Debug.LogWarning(LogTag + " " + msg);
}
/// <summary>
/// Print an error message with the tag "VIVE.OpenXR.SecondaryViewConfiguration."
/// </summary>
/// <param name="msg">The error message you want to print.</param>
private static void Error(string msg)
{
UnityEngine.Debug.LogError(LogTag + " " + msg);
}
/// <summary>
/// Get the SpectatorCameraBased component in the current Unity scene.
/// </summary>
/// <returns>SpectatorCameraBased array if there are any SpectatorCameraBased components. Otherwise, return null.</returns>
private static SpectatorCameraBased[] GetSpectatorCameraBased()
{
var spectatorCameraBasedArray = (SpectatorCameraBased[])FindObjectsOfType(typeof(SpectatorCameraBased));
return (spectatorCameraBasedArray != null && spectatorCameraBasedArray.Length > 0)
? spectatorCameraBasedArray
: null;
}
/// <summary>
/// Create a GameObject that includes SpectatorCameraBased script in Unity scene for cooperation with extension native plugins.
/// </summary>
private static void CreateSpectatorCameraBased()
{
if (IsExtensionEnabled())
{
Debug($"Instance.IsExistSpectatorCameraBased = {Instance.IsExistSpectatorCameraBased}");
Instance.IsExistSpectatorCameraBased = true;
if (GetSpectatorCameraBased() != null)
{
Debug("No need to add SpectatorCameraBased because the scene already exist.");
return;
}
Debug("Start to add SpectatorCameraBased.");
var spectatorCameraBase =
new GameObject("Spectator Camera Base", typeof(SpectatorCameraBased))
{
transform =
{
position = Vector3.zero,
rotation = Quaternion.identity
}
};
Debug($"Create Spectator Camera Base GameObject successfully: {spectatorCameraBase != null}");
Debug(
$"Included SpectatorCameraBased component: {spectatorCameraBase.GetComponent<SpectatorCameraBased>() != null}");
}
else
{
Debug("Create Spectator Camera Base GameObject failed because the related extensions are not enabled.");
}
}
#endregion
#endregion
#region Enum definition
/// <summary>
/// The enum definition of supporting rendering backend.
/// </summary>
private enum GraphicsAPI
{
Unknown = 0,
GLES3 = 1,
Vulkan = 2
}
#endregion
}
}

View File

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