version 2.5.0
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69583d931f8502647afcc5c3266f4d2c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a040f975f97360649b37c7c9706d3970
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47a6afbbc5e705041b2851122f306f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d6f293344377c74c910f4121af00a7b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 application’s 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f3afcc00e190534d8c0c8ebd2246d84
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user