Files
VIVE-OpenXR-Unity/com.htc.upm.vive.openxr/Runtime/CompositionLayer/Scripts/CompositionLayer.cs
2023-09-14 18:17:47 +08:00

1853 lines
56 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// "VIVE SDK
// © 2020 HTC Corporation. All Rights Reserved.
//
// Unless otherwise required by copyright law and practice,
// upon the execution of HTC SDK license agreement,
// HTC grants you access to and use of the VIVE SDK(s).
// You shall fully comply with all of HTCs SDK license agreement terms and
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR;
using UnityEngine.XR.OpenXR;
using System.Collections.Generic;
using System.Linq;
using VIVE.OpenXR.CompositionLayer;
namespace VIVE.OpenXR.CompositionLayer
{
public class CompositionLayer : MonoBehaviour
{
#if UNITY_EDITOR
[SerializeField]
public bool isPreviewingCylinder = false;
[SerializeField]
public bool isPreviewingQuad = false;
[SerializeField]
public GameObject generatedPreview = null;
public const string CylinderPreviewName = "CylinderPreview";
public const string QuadPreviewName = "QuadPreview";
#endif
public const string CylinderUnderlayMeshName = "Underlay Alpha Mesh (Cylinder)";
public const string QuadUnderlayMeshName = "Underlay Alpha Mesh (Quad)";
public const string FallbackMeshName = "FallbackMeshGO";
[SerializeField]
public LayerType layerType = LayerType.Overlay;
[SerializeField]
public uint compositionDepth = 0;
[SerializeField]
public LayerShape layerShape = LayerShape.Quad;
[SerializeField]
public Visibility layerVisibility = Visibility.Both;
[SerializeField]
[Min(0.001f)]
private float m_QuadWidth = 1f;
public float quadWidth { get { return m_QuadWidth; } }
#if UNITY_EDITOR
public float QuadWidth { get { return m_QuadWidth; } set { m_QuadWidth = value; } }
#endif
[SerializeField]
[Min(0.001f)]
private float m_QuadHeight = 1f;
public float quadHeight { get { return m_QuadHeight; } }
#if UNITY_EDITOR
public float QuadHeight { get { return m_QuadHeight; } set { m_QuadHeight = value; } }
#endif
[SerializeField]
[Min(0.001f)]
private float m_CylinderHeight = 1f;
public float cylinderHeight { get { return m_CylinderHeight; } }
#if UNITY_EDITOR
public float CylinderHeight { get { return m_CylinderHeight; } set { m_CylinderHeight = value; } }
#endif
[SerializeField]
[Min(0.001f)]
private float m_CylinderArcLength = 1f;
public float cylinderArcLength { get { return m_CylinderArcLength; } }
#if UNITY_EDITOR
public float CylinderArcLength { get { return m_CylinderArcLength; } set { m_CylinderArcLength = value; } }
#endif
[SerializeField]
[Min(0.001f)]
private float m_CylinderRadius = 1f;
public float cylinderRadius { get { return m_CylinderRadius; } }
#if UNITY_EDITOR
public float CylinderRadius { get { return m_CylinderRadius; } set { m_CylinderRadius = value; } }
#endif
[SerializeField]
[Range(0f, 360f)]
private float m_CylinderAngleOfArc = 180f;
public float cylinderAngleOfArc { get { return m_CylinderAngleOfArc; } }
#if UNITY_EDITOR
public float CylinderAngleOfArc { get { return m_CylinderAngleOfArc; } set { m_CylinderAngleOfArc = value; } }
#endif
#if UNITY_EDITOR
[SerializeField]
public CylinderLayerParamLockMode lockMode = CylinderLayerParamLockMode.ArcLength;
#endif
[SerializeField]
public bool isDynamicLayer = false;
[SerializeField]
public bool applyColorScaleBias = false;
[SerializeField]
public Color colorScale = Color.white;
[SerializeField]
public Color colorBias = Color.clear;
[SerializeField]
public bool isProtectedSurface = false;
[SerializeField]
public Texture texture = null;
[SerializeField]
private uint renderPriority = 0;
public uint GetRenderPriority() { return renderPriority; }
public void SetRenderPriority(uint newRenderPriority)
{
renderPriority = newRenderPriority;
isRenderPriorityChanged = true;
CompositionLayerManager.GetInstance().SubscribeToLayerManager(this);
}
public bool isRenderPriorityChanged
{
get; private set;
}
[SerializeField]
public GameObject trackingOrigin = null;
public GameObject generatedUnderlayMesh = null;
private MeshRenderer generatedUnderlayMeshRenderer = null;
private MeshFilter generatedUnderlayMeshFilter = null;
public GameObject generatedFallbackMesh = null;
private MeshRenderer generatedFallbackMeshRenderer = null;
private MeshFilter generatedFallbackMeshFilter = null;
private LayerTextures layerTextures;
private Material texture2DBlitMaterial;
private GameObject compositionLayerPlaceholderPrefabGO = null;
public readonly float angleOfArcLowerLimit = 1f;
public readonly float angleOfArcUpperLimit = 360f;
private LayerShape previousLayerShape = LayerShape.Quad;
private float previousQuadWidth = 1f;
private float previousQuadHeight = 1f;
private float previousCylinderHeight = 1f;
private float previousCylinderArcLength = 1f;
private float previousCylinderRadius = 1f;
private float previousAngleOfArc = 180f;
private Texture previousTexture = null;
private bool previousIsDynamicLayer = false;
private int layerID; //For native
private bool isHeadLock = false;
private bool InitStatus = false;
private bool isInitializationComplete = false;
private bool reinitializationNeeded = false;
private bool isOnBeforeRenderSubscribed = false;
private bool isLayerReadyForSubmit = false;
private bool isLinear = false;
private bool isAutoFallbackActive = false;
private bool placeholderGenerated = false;
private static bool isSynchronized = false;
private static RenderThreadSynchronizer synchronizer;
private Camera hmd;
private ViveCompositionLayer compositionLayerFeature = null;
private const string LOG_TAG = "VIVE_CompositionLayer";
static void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
static void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); }
static void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
#region Composition Layer Lifecycle
private bool CompositionLayerInit()
{
if (isInitializationComplete)
{
//DEBUG("CompositionLayerInit: Already initialized.");
return true;
}
if (texture == null)
{
ERROR("CompositionLayerInit: Source Texture not found, abort init.");
return false;
}
DEBUG("CompositionLayerInit");
uint textureWidth = (uint)texture.width;
uint textureHeight = (uint)texture.height;
CompositionLayerRenderThreadSyncObject ObtainLayerSwapchainSyncObject = new CompositionLayerRenderThreadSyncObject(
(taskQueue) =>
{
lock (taskQueue)
{
CompositionLayerRenderThreadTask task = (CompositionLayerRenderThreadTask)taskQueue.Dequeue();
//Enumerate Swapchain formats
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
uint imageCount;
GraphicsAPI graphicsAPI = GraphicsAPI.GLES3;
switch(SystemInfo.graphicsDeviceType)
{
case UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3:
graphicsAPI = GraphicsAPI.GLES3;
break;
case UnityEngine.Rendering.GraphicsDeviceType.Vulkan:
graphicsAPI = GraphicsAPI.Vulkan;
break;
default:
ERROR("Unsupported Graphics API, aborting init process.");
return;
}
layerID = compositionLayerFeature.CompositionLayer_Init(textureWidth, textureHeight, graphicsAPI, isDynamicLayer, isProtectedSurface, out imageCount);
if (layerID != 0)
{
DEBUG("Init completed, ID: " + layerID + ", Image Count: " + imageCount);
layerTextures = new LayerTextures(imageCount);
InitStatus = true;
}
taskQueue.Release(task);
}
});
CompositionLayerRenderThreadTask.IssueObtainSwapchainEvent(ObtainLayerSwapchainSyncObject);
previousLayerShape = layerShape;
previousQuadWidth = m_QuadWidth;
previousQuadHeight = m_QuadHeight;
previousCylinderHeight = m_CylinderHeight;
previousCylinderArcLength = m_CylinderArcLength;
previousCylinderRadius = m_CylinderRadius;
previousAngleOfArc = m_CylinderAngleOfArc;
previousTexture = texture;
previousIsDynamicLayer = isDynamicLayer;
return true;
}
private bool textureAcquired = false;
private bool textureAcquiredOnce = false;
XrOffset2Di offset = new XrOffset2Di();
XrExtent2Di extent = new XrExtent2Di();
XrRect2Di rect = new XrRect2Di();
private bool SetLayerTexture()
{
if (!isInitializationComplete || !isSynchronized) return false;
if (texture != null) //check for texture size change
{
if (TextureParamsChanged())
{
//Destroy queues
DEBUG("SetLayerTexture: Texture params changed, need to re-init queues. layerID: " + layerID);
DestroyCompositionLayer();
reinitializationNeeded = true;
return false;
}
}
else
{
ERROR("SetLayerTexture: No texture found. layerID: " + layerID);
return false;
}
if (isDynamicLayer || (!isDynamicLayer && !textureAcquiredOnce))
{
//Get available texture id
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
uint currentImageIndex;
IntPtr newTextureID = compositionLayerFeature.CompositionLayer_GetTexture(layerID, out currentImageIndex);
textureAcquired = true;
textureAcquiredOnce = true;
if (newTextureID == IntPtr.Zero)
{
ERROR("SetLayerTexture: Invalid Texture ID");
if (compositionLayerFeature.CompositionLayer_ReleaseTexture(layerID))
{
textureAcquired = false;
}
return false;
}
bool textureIDUpdated = false;
layerTextures.currentAvailableTextureIndex = currentImageIndex;
IntPtr currentTextureID = layerTextures.GetCurrentAvailableTextureID();
if (currentTextureID == IntPtr.Zero || currentTextureID != newTextureID)
{
DEBUG("SetLayerTexture: Update Texture ID. layerID: " + layerID);
layerTextures.SetCurrentAvailableTextureID(newTextureID);
textureIDUpdated = true;
}
if (layerTextures.GetCurrentAvailableTextureID() == IntPtr.Zero)
{
ERROR("SetLayerTexture: Failed to get texture.");
return false;
}
// Create external texture
if (layerTextures.GetCurrentAvailableExternalTexture() == null || textureIDUpdated)
{
DEBUG("SetLayerTexture: Create External Texture.");
layerTextures.SetCurrentAvailableExternalTexture(Texture2D.CreateExternalTexture(texture.width, texture.height, TextureFormat.RGBA32, false, isLinear, layerTextures.GetCurrentAvailableTextureID()));
}
if (layerTextures.externalTextures[layerTextures.currentAvailableTextureIndex] == null)
{
ERROR("SetLayerTexture: Create External Texture Failed.");
return false;
}
}
//Set Texture Content
bool isContentSet = layerTextures.textureContentSet[layerTextures.currentAvailableTextureIndex];
if (!isDynamicLayer && isContentSet)
{
return true;
}
int currentTextureWidth = layerTextures.GetCurrentAvailableExternalTexture().width;
int currentTextureHeight = layerTextures.GetCurrentAvailableExternalTexture().height;
//Set Texture Layout
offset.x = 0;
offset.y = 0;
extent.width = (int)currentTextureWidth;
extent.height = (int)currentTextureHeight;
rect.offset = offset;
rect.extent = extent;
layerTextures.textureLayout = rect;
//Blit and copy texture
RenderTexture srcTexture = texture as RenderTexture;
int msaaSamples = 1;
if (srcTexture != null)
{
msaaSamples = srcTexture.antiAliasing;
}
Material currentBlitMat = texture2DBlitMaterial;
RenderTextureDescriptor rtDescriptor = new RenderTextureDescriptor(currentTextureWidth, currentTextureHeight, RenderTextureFormat.ARGB32, 0);
rtDescriptor.msaaSamples = msaaSamples;
rtDescriptor.autoGenerateMips = false;
rtDescriptor.useMipMap = false;
rtDescriptor.sRGB = false;
RenderTexture blitTempRT = RenderTexture.GetTemporary(rtDescriptor);
if (!blitTempRT.IsCreated())
{
blitTempRT.Create();
}
blitTempRT.DiscardContents();
Texture dstTexture = layerTextures.GetCurrentAvailableExternalTexture();
Graphics.Blit(texture, blitTempRT, currentBlitMat);
Graphics.CopyTexture(blitTempRT, 0, 0, dstTexture, 0, 0);
//DEBUG("Blit and CopyTexture complete.");
if (blitTempRT != null)
{
RenderTexture.ReleaseTemporary(blitTempRT);
}
else
{
ERROR("blitTempRT not found");
return false;
}
layerTextures.textureContentSet[layerTextures.currentAvailableTextureIndex] = true;
bool releaseTextureResult = compositionLayerFeature.CompositionLayer_ReleaseTexture(layerID);
if (releaseTextureResult)
{
textureAcquired = false;
}
return releaseTextureResult;
}
private void GetCompositionLayerPose(ref XrPosef pose) //Call at onBeforeRender
{
//Check if overlay is child of hmd transform i.e. head-lock
Transform currentTransform = transform;
isHeadLock = false;
while (currentTransform != null)
{
if (currentTransform == hmd.transform) //Overlay is child of hmd transform
{
isHeadLock = true;
break;
}
currentTransform = currentTransform.parent;
}
if (isHeadLock)
{
//Calculate Layer Rotation and Position relative to Camera
Vector3 relativePosition = hmd.transform.InverseTransformPoint(transform.position);
Quaternion relativeRotation = Quaternion.Inverse(hmd.transform.rotation).normalized * transform.rotation.normalized;
UnityToOpenXRConversionHelper.GetOpenXRVector(relativePosition, ref pose.position);
UnityToOpenXRConversionHelper.GetOpenXRQuaternion(relativeRotation.normalized, ref pose.orientation);
}
else
{
Vector3 trackingSpacePosition = transform.position;
Quaternion trackingSpaceRotation = transform.rotation;
if (trackingOrigin != null) //Apply origin correction to the layer pose
{
Matrix4x4 trackingSpaceOriginTRS = Matrix4x4.TRS(trackingOrigin.transform.position, trackingOrigin.transform.rotation, Vector3.one);
Matrix4x4 worldSpaceLayerPoseTRS = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
Matrix4x4 trackingSpaceLayerPoseTRS = trackingSpaceOriginTRS.inverse * worldSpaceLayerPoseTRS;
trackingSpacePosition = trackingSpaceLayerPoseTRS.GetColumn(3); //4th Column of TRS Matrix is the position
trackingSpaceRotation = Quaternion.LookRotation(trackingSpaceLayerPoseTRS.GetColumn(2), trackingSpaceLayerPoseTRS.GetColumn(1));
}
UnityToOpenXRConversionHelper.GetOpenXRVector(trackingSpacePosition, ref pose.position);
UnityToOpenXRConversionHelper.GetOpenXRQuaternion(trackingSpaceRotation.normalized, ref pose.orientation);
}
}
bool enabledColorScaleBiasInShader = false;
XrCompositionLayerColorScaleBiasKHR CompositionLayerParamsColorScaleBias = new XrCompositionLayerColorScaleBiasKHR();
private void SubmitCompositionLayer() //Call at onBeforeRender
{
if (!isInitializationComplete && !isLayerReadyForSubmit) return;
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
if (isInitializationComplete && isSynchronized)
{
ViveCompositionLayerColorScaleBias compositionLayerColorScaleBias = OpenXRSettings.Instance.GetFeature<ViveCompositionLayerColorScaleBias>();
if (applyColorScaleBias && compositionLayerColorScaleBias.ColorScaleBiasExtensionEnabled)
{
if (layerType == LayerType.Underlay)
{
if (!enabledColorScaleBiasInShader)
{
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
{
generatedUnderlayMeshRenderer.material.EnableKeyword("COLOR_SCALE_BIAS_ENABLED");
enabledColorScaleBiasInShader = true;
}
}
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
{
generatedUnderlayMeshRenderer.material.SetColor("_ColorScale", colorScale);
generatedUnderlayMeshRenderer.material.SetColor("_ColorBias", colorBias);
}
}
else if (enabledColorScaleBiasInShader) //Disable if not underlay
{
generatedUnderlayMeshRenderer.material.DisableKeyword("COLOR_SCALE_BIAS_ENABLED");
enabledColorScaleBiasInShader = false;
}
CompositionLayerParamsColorScaleBias.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR;
UnityToOpenXRConversionHelper.GetOpenXRColor4f(colorScale, ref CompositionLayerParamsColorScaleBias.colorScale);
UnityToOpenXRConversionHelper.GetOpenXRColor4f(colorBias, ref CompositionLayerParamsColorScaleBias.colorBias);
compositionLayerColorScaleBias.Submit_CompositionLayerColorBias(CompositionLayerParamsColorScaleBias, layerID);
}
else if (enabledColorScaleBiasInShader) //Disable if color scale bias is no longer active
{
generatedUnderlayMeshRenderer.material.DisableKeyword("COLOR_SCALE_BIAS_ENABLED");
enabledColorScaleBiasInShader = false;
}
switch (layerShape)
{
default:
case LayerShape.Quad:
compositionLayerFeature.Submit_CompositionLayerQuad(AssignCompositionLayerParamsQuad(), (OpenXR.CompositionLayer.LayerType)layerType, compositionDepth, layerID);
break;
case LayerShape.Cylinder:
ViveCompositionLayerCylinder compositionLayerCylinderFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayerCylinder>();
if (compositionLayerCylinderFeature != null && compositionLayerCylinderFeature.CylinderExtensionEnabled)
{
compositionLayerCylinderFeature.Submit_CompositionLayerCylinder(AssignCompositionLayerParamsCylinder(), (OpenXR.CompositionLayer.LayerType)layerType, compositionDepth, layerID);
}
break;
}
}
}
public delegate void OnDestroyCompositionLayer();
public event OnDestroyCompositionLayer OnDestroyCompositionLayerDelegate = null;
private void DestroyCompositionLayer()
{
if (!isInitializationComplete || layerTextures == null)
{
DEBUG("DestroyCompositionLayer: Layer already destroyed/not initialized.");
return;
}
DEBUG("DestroyCompositionLayer");
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
if (textureAcquired)
{
DEBUG("DestroyCompositionLayer: textureAcquired, releasing.");
textureAcquired = !compositionLayerFeature.CompositionLayer_ReleaseTexture(layerID);
}
CompositionLayerRenderThreadSyncObject DestroyLayerSwapchainSyncObject = new CompositionLayerRenderThreadSyncObject(
(taskQueue) =>
{
lock (taskQueue)
{
CompositionLayerRenderThreadTask task = (CompositionLayerRenderThreadTask)taskQueue.Dequeue();
//Enumerate Swapchain formats
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
if (!compositionLayerFeature.CompositionLayer_Destroy(task.layerID))
{
ERROR("estroyCompositionLayer: CompositionLayer_Destroy failed.");
}
taskQueue.Release(task);
}
});
CompositionLayerRenderThreadTask.IssueDestroySwapchainEvent(DestroyLayerSwapchainSyncObject, layerID);
InitStatus = false;
isLayerReadyForSubmit = false;
isInitializationComplete = false;
textureAcquiredOnce = false;
foreach (Texture externalTexture in layerTextures.externalTextures)
{
DEBUG("DestroyCompositionLayer: External textures");
if (externalTexture != null) Destroy(externalTexture);
}
layerTextures = null;
if (generatedFallbackMeshFilter != null && generatedFallbackMeshFilter.mesh != null)
{
DEBUG("DestroyCompositionLayer: generatedFallbackMeshFilter");
Destroy(generatedFallbackMeshFilter.mesh);
generatedFallbackMeshFilter = null;
}
if (generatedFallbackMeshRenderer != null && generatedFallbackMeshRenderer.material != null)
{
DEBUG("DestroyCompositionLayer: generatedFallbackMeshRenderer");
Destroy(generatedFallbackMeshRenderer.material);
generatedFallbackMeshRenderer = null;
}
if (generatedUnderlayMeshFilter != null && generatedUnderlayMeshFilter.mesh != null)
{
DEBUG("DestroyCompositionLayer: generatedUnderlayMeshFilter");
Destroy(generatedUnderlayMeshFilter.mesh);
generatedUnderlayMeshFilter = null;
}
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
{
DEBUG("DestroyCompositionLayer: generatedUnderlayMeshRenderer");
Destroy(generatedUnderlayMeshRenderer.material);
generatedUnderlayMeshRenderer = null;
}
if (generatedFallbackMesh != null)
{
Destroy(generatedFallbackMesh);
generatedFallbackMesh = null;
}
if (generatedUnderlayMesh != null)
{
Destroy(generatedUnderlayMesh);
generatedUnderlayMesh = null;
}
OnDestroyCompositionLayerDelegate?.Invoke();
}
private List<XRInputSubsystem> inputSubsystems = new List<XRInputSubsystem>();
XrCompositionLayerQuad CompositionLayerParamsQuad = new XrCompositionLayerQuad();
XrExtent2Df quadSize = new XrExtent2Df();
private XrCompositionLayerQuad AssignCompositionLayerParamsQuad()
{
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
CompositionLayerParamsQuad.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_QUAD;
CompositionLayerParamsQuad.layerFlags = ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT | ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
switch (layerVisibility)
{
default:
case Visibility.Both:
CompositionLayerParamsQuad.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_BOTH;
break;
case Visibility.Left:
CompositionLayerParamsQuad.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_LEFT;
break;
case Visibility.Right:
CompositionLayerParamsQuad.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_RIGHT;
break;
}
CompositionLayerParamsQuad.subImage.imageRect = layerTextures.textureLayout;
CompositionLayerParamsQuad.subImage.imageArrayIndex = 0;
GetCompositionLayerPose(ref CompositionLayerParamsQuad.pose); //Update isHeadLock
if (isHeadLock)
{
CompositionLayerParamsQuad.space = compositionLayerFeature.HeadLockSpace;
}
else
{
XRInputSubsystem subsystem = null;
SubsystemManager.GetInstances(inputSubsystems);
if (inputSubsystems.Count > 0)
{
subsystem = inputSubsystems[0];
}
if (subsystem != null)
{
TrackingOriginModeFlags trackingOriginMode = subsystem.GetTrackingOriginMode();
switch (trackingOriginMode)
{
default:
case TrackingOriginModeFlags.Floor:
CompositionLayerParamsQuad.space = compositionLayerFeature.WorldLockSpaceOriginOnFloor;
break;
case TrackingOriginModeFlags.Device:
CompositionLayerParamsQuad.space = compositionLayerFeature.WorldLockSpaceOriginOnHead;
break;
}
}
else
{
CompositionLayerParamsQuad.space = compositionLayerFeature.WorldLockSpaceOriginOnFloor;
}
}
quadSize.width = m_QuadWidth;
quadSize.height = m_QuadHeight;
CompositionLayerParamsQuad.size = quadSize;
return CompositionLayerParamsQuad;
}
XrCompositionLayerCylinderKHR CompositionLayerParamsCylinder = new XrCompositionLayerCylinderKHR();
private XrCompositionLayerCylinderKHR AssignCompositionLayerParamsCylinder()
{
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
CompositionLayerParamsCylinder.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR;
CompositionLayerParamsCylinder.layerFlags = ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT | ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
if (isHeadLock)
{
CompositionLayerParamsCylinder.space = compositionLayerFeature.HeadLockSpace;
}
else
{
XRInputSubsystem subsystem = null;
SubsystemManager.GetInstances(inputSubsystems);
if (inputSubsystems.Count > 0)
{
subsystem = inputSubsystems[0];
}
if (subsystem != null)
{
TrackingOriginModeFlags trackingOriginMode = subsystem.GetTrackingOriginMode();
switch (trackingOriginMode)
{
default:
case TrackingOriginModeFlags.Floor:
DEBUG("TrackingOriginModeFlags.Floor");
CompositionLayerParamsCylinder.space = compositionLayerFeature.WorldLockSpaceOriginOnFloor;
break;
case TrackingOriginModeFlags.Device:
DEBUG("TrackingOriginModeFlags.Device");
CompositionLayerParamsCylinder.space = compositionLayerFeature.WorldLockSpaceOriginOnHead;
break;
}
}
else
{
CompositionLayerParamsCylinder.space = compositionLayerFeature.WorldLockSpaceOriginOnFloor;
}
}
switch (layerVisibility)
{
default:
case Visibility.Both:
CompositionLayerParamsCylinder.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_BOTH;
break;
case Visibility.Left:
CompositionLayerParamsCylinder.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_LEFT;
break;
case Visibility.Right:
CompositionLayerParamsCylinder.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_RIGHT;
break;
}
CompositionLayerParamsCylinder.subImage.imageRect = layerTextures.textureLayout;
CompositionLayerParamsCylinder.subImage.imageArrayIndex = 0;
GetCompositionLayerPose(ref CompositionLayerParamsCylinder.pose);
CompositionLayerParamsCylinder.radius = m_CylinderRadius;
CompositionLayerParamsCylinder.centralAngle = Mathf.Deg2Rad * m_CylinderAngleOfArc;
CompositionLayerParamsCylinder.aspectRatio = m_CylinderArcLength / m_CylinderHeight;
return CompositionLayerParamsCylinder;
}
private void ActivatePlaceholder()
{
if (Debug.isDebugBuild && !placeholderGenerated)
{
if (CompositionLayerManager.GetInstance().MaxLayerCount() == 0)//Platform does not support multi-layer. Show placeholder instead if in development build
{
DEBUG("Generate Placeholder");
compositionLayerPlaceholderPrefabGO = Instantiate((GameObject)Resources.Load("Prefabs/CompositionLayerDebugPlaceholder", typeof(GameObject)));
compositionLayerPlaceholderPrefabGO.name = "CompositionLayerDebugPlaceholder";
compositionLayerPlaceholderPrefabGO.transform.SetParent(this.transform);
compositionLayerPlaceholderPrefabGO.transform.position = this.transform.position;
compositionLayerPlaceholderPrefabGO.transform.rotation = this.transform.rotation;
compositionLayerPlaceholderPrefabGO.transform.localScale = this.transform.localScale;
Text placeholderText = compositionLayerPlaceholderPrefabGO.transform.GetChild(0).Find("Text").GetComponent<Text>();
placeholderText.text = placeholderText.text.Replace("{REASON}", "Device does not support Multi-Layer");
placeholderGenerated = true;
}
else if (CompositionLayerManager.GetInstance().MaxLayerCount() <= CompositionLayerManager.GetInstance().CurrentLayerCount())//Do not draw layer as limit is reached. Show placeholder instead if in development build
{
DEBUG("Generate Placeholder");
compositionLayerPlaceholderPrefabGO = Instantiate((GameObject)Resources.Load("Prefabs/CompositionLayerDebugPlaceholder", typeof(GameObject)));
compositionLayerPlaceholderPrefabGO.name = "CompositionLayerDebugPlaceholder";
compositionLayerPlaceholderPrefabGO.transform.SetParent(this.transform);
compositionLayerPlaceholderPrefabGO.transform.position = this.transform.position;
compositionLayerPlaceholderPrefabGO.transform.rotation = this.transform.rotation;
compositionLayerPlaceholderPrefabGO.transform.localScale = this.transform.localScale;
Text placeholderText = compositionLayerPlaceholderPrefabGO.transform.GetChild(0).Find("Text").GetComponent<Text>();
placeholderText.text = placeholderText.text.Replace("{REASON}", "Max number of layers exceeded");
placeholderGenerated = true;
}
}
else if (placeholderGenerated && compositionLayerPlaceholderPrefabGO != null)
{
DEBUG("Placeholder already generated, activating.");
compositionLayerPlaceholderPrefabGO.SetActive(true);
}
}
public bool RenderAsLayer()
{
if (placeholderGenerated && compositionLayerPlaceholderPrefabGO != null)
{
compositionLayerPlaceholderPrefabGO.SetActive(false);
}
if (isAutoFallbackActive)
{
generatedFallbackMesh.SetActive(false);
isAutoFallbackActive = false;
}
isRenderPriorityChanged = false;
//if Underlay Mesh is present but needs to be reconstructed
if (layerType == LayerType.Underlay)
{
if (!UnderlayMeshIsValid()) //Underlay Mesh needs to be generated
{
UnderlayMeshGeneration();
}
else if (LayerDimensionsChanged()) //if Underlay Mesh is present but needs to be updated
{
UnderlayMeshUpdate();
}
else generatedUnderlayMesh.SetActive(true);
}
return CompositionLayerInit();
}
public void RenderInGame()
{
compositionLayerFeature = compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
if (compositionLayerFeature.enableAutoFallback)
{
if (!isAutoFallbackActive)
{
//Activate auto fallback
//Activate auto fallback
if (!FallbackMeshIsValid())
{
AutoFallbackMeshGeneration();
}
else if (LayerDimensionsChanged())
{
AutoFallbackMeshUpdate();
}
generatedFallbackMesh.SetActive(true);
isAutoFallbackActive = true;
}
}
else //Use placeholder
{
ActivatePlaceholder();
}
if (layerType == LayerType.Underlay && generatedUnderlayMesh != null)
{
generatedUnderlayMesh.SetActive(false);
}
isRenderPriorityChanged = false;
}
public void TerminateLayer()
{
DEBUG("TerminateLayer: layerID: " + layerID);
DestroyCompositionLayer();
if (placeholderGenerated && compositionLayerPlaceholderPrefabGO != null)
{
compositionLayerPlaceholderPrefabGO.SetActive(false);
}
if (isAutoFallbackActive)
{
generatedFallbackMesh.SetActive(false);
isAutoFallbackActive = false;
}
}
public bool TextureParamsChanged()
{
if (previousTexture != texture)
{
previousTexture = texture;
return true;
}
if (previousIsDynamicLayer != isDynamicLayer)
{
previousIsDynamicLayer = isDynamicLayer;
return true;
}
return false;
}
public bool LayerDimensionsChanged()
{
bool isChanged = false;
if (layerShape == LayerShape.Cylinder)
{
if (previousAngleOfArc != m_CylinderAngleOfArc ||
previousCylinderArcLength != m_CylinderArcLength ||
previousCylinderHeight != m_CylinderHeight ||
previousCylinderRadius != m_CylinderRadius)
{
previousAngleOfArc = m_CylinderAngleOfArc;
previousCylinderArcLength = m_CylinderArcLength;
previousCylinderHeight = m_CylinderHeight;
previousCylinderRadius = m_CylinderRadius;
isChanged = true;
}
}
else if (layerShape == LayerShape.Quad)
{
if (previousQuadWidth != m_QuadWidth ||
previousQuadHeight != m_QuadHeight)
{
previousQuadWidth = m_QuadWidth;
previousQuadHeight = m_QuadHeight;
isChanged = true;
}
}
if (previousLayerShape != layerShape)
{
previousLayerShape = layerShape;
isChanged = true;
}
return isChanged;
}
#region Quad Runtime Parameter Change
/// <summary>
/// Use this function to update the width of a Quad Layer.
/// </summary>
/// <param name="inWidth"></param>
/// <returns></returns>
public bool SetQuadLayerWidth(float inWidth)
{
if (inWidth <= 0)
{
return false;
}
m_QuadWidth = inWidth;
return true;
}
/// <summary>
/// Use this function to update the height of a Quad Layer.
/// </summary>
/// <param name="inHeight"></param>
/// <returns></returns>
public bool SetQuadLayerHeight(float inHeight)
{
if (inHeight <= 0)
{
return false;
}
m_QuadHeight = inHeight;
return true;
}
#endregion
#region Cylinder Runtime Parameter Change
/// <summary>
/// Use this function to update the radius and arc angle of a Cylinder Layer.
/// A new arc length will be calculated automatically.
/// </summary>
/// <param name="inRadius"></param>
/// <param name="inArcAngle"></param>
/// <returns>True if the parameters are valid and successfully updated.</returns>
public bool SetCylinderLayerRadiusAndArcAngle(float inRadius, float inArcAngle)
{
//Check if radius is valid
if (inRadius <= 0)
{
return false;
}
//Check if angle of arc is valid
if (inArcAngle < angleOfArcLowerLimit || inArcAngle > angleOfArcUpperLimit)
{
return false;
}
//Check if new arc length is valid
float newArcLength = CylinderParameterHelper.RadiusAndDegAngleOfArcToArcLength(inArcAngle, inRadius);
if (newArcLength <= 0)
{
return false;
}
//All parameters are valid, assign to layer
m_CylinderArcLength = newArcLength;
m_CylinderRadius = inRadius;
m_CylinderAngleOfArc = inArcAngle;
return true;
}
/// <summary>
/// Use this function to update the radius and arc length of a Cylinder Layer.
/// A new arc angle will be calculated automatically.
/// </summary>
/// <param name="inRadius"></param>
/// <param name="inArcLength"></param>
/// <returns>True if the parameters are valid and successfully updated.</returns>
public bool SetCylinderLayerRadiusAndArcLength(float inRadius, float inArcLength)
{
//Check if radius is valid
if (inRadius <= 0)
{
return false;
}
//Check if arc length is valid
if (inArcLength <= 0)
{
return false;
}
//Check if new arc angle is valid
float newArcAngle = CylinderParameterHelper.RadiusAndArcLengthToDegAngleOfArc(inArcLength, inRadius);
if (newArcAngle < angleOfArcLowerLimit || newArcAngle > angleOfArcUpperLimit)
{
return false;
}
//All parameters are valid, assign to layer
m_CylinderArcLength = inArcLength;
m_CylinderRadius = inRadius;
m_CylinderAngleOfArc = newArcAngle;
return true;
}
/// <summary>
/// Use this function to update the arc angle and arc length of a Cylinder Layer.
/// A new radius will be calculated automatically.
/// </summary>
/// <param name="inArcAngle"></param>
/// <param name="inArcLength"></param>
/// <returns>True if the parameters are valid and successfully updated.</returns>
public bool SetCylinderLayerArcAngleAndArcLength(float inArcAngle, float inArcLength)
{
//Check if arc angle is valid
if (inArcAngle < angleOfArcLowerLimit || inArcAngle > angleOfArcUpperLimit)
{
return false;
}
//Check if arc length is valid
if (inArcLength <= 0)
{
return false;
}
//Check if new radius is valid
float newRadius = CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(inArcLength, inArcAngle);
if (newRadius <= 0)
{
return false;
}
//All parameters are valid, assign to layer
m_CylinderArcLength = inArcLength;
m_CylinderRadius = newRadius;
m_CylinderAngleOfArc = inArcAngle;
return true;
}
/// <summary>
/// Use this function to update the height of a Cylinder Layer.
/// </summary>
/// <param name="inHeight"></param>
/// <returns></returns>
public bool SetCylinderLayerHeight(float inHeight)
{
if (inHeight <= 0)
{
return false;
}
m_CylinderHeight = inHeight;
return true;
}
#endregion
#if UNITY_EDITOR
public CylinderLayerParamAdjustmentMode CurrentAdjustmentMode()
{
if (previousCylinderArcLength != m_CylinderArcLength)
{
return CylinderLayerParamAdjustmentMode.ArcLength;
}
else if (previousAngleOfArc != m_CylinderAngleOfArc)
{
return CylinderLayerParamAdjustmentMode.ArcAngle;
}
else
{
return CylinderLayerParamAdjustmentMode.Radius;
}
}
#endif
public void ChangeBlitShadermode(BlitShaderMode shaderMode, bool enable)
{
if (texture2DBlitMaterial == null) return;
switch (shaderMode)
{
case BlitShaderMode.LINEAR_TO_SRGB_COLOR:
if (enable)
{
texture2DBlitMaterial.EnableKeyword("LINEAR_TO_SRGB_COLOR");
}
else
{
texture2DBlitMaterial.DisableKeyword("LINEAR_TO_SRGB_COLOR");
}
break;
case BlitShaderMode.LINEAR_TO_SRGB_ALPHA:
if (enable)
{
texture2DBlitMaterial.EnableKeyword("LINEAR_TO_SRGB_ALPHA");
}
else
{
texture2DBlitMaterial.DisableKeyword("LINEAR_TO_SRGB_ALPHA");
}
break;
default:
break;
}
}
#endregion
#region Monobehavior Lifecycle
private void Awake()
{
//Create blit mat
texture2DBlitMaterial = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/Texture2DBlitShader"));
//Create render thread synchornizer
if (synchronizer == null) synchronizer = new RenderThreadSynchronizer();
ColorSpace currentActiveColorSpace = QualitySettings.activeColorSpace;
if (currentActiveColorSpace == ColorSpace.Linear)
{
isLinear = true;
}
else
{
isLinear = false;
}
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
}
private void OnEnable()
{
hmd = Camera.main;
CompositionLayerManager.GetInstance().SubscribeToLayerManager(this);
if (!isOnBeforeRenderSubscribed)
{
CompositionLayerManager.GetInstance().CompositionLayerManagerOnBeforeRenderDelegate += OnBeforeRender;
isOnBeforeRenderSubscribed = true;
}
}
private void OnDisable()
{
if (isOnBeforeRenderSubscribed)
{
CompositionLayerManager.GetInstance().CompositionLayerManagerOnBeforeRenderDelegate -= OnBeforeRender;
isOnBeforeRenderSubscribed = false;
}
CompositionLayerManager.GetInstance().UnsubscribeFromLayerManager(this, false);
}
private void OnDestroy()
{
if (isOnBeforeRenderSubscribed)
{
CompositionLayerManager.GetInstance().CompositionLayerManagerOnBeforeRenderDelegate -= OnBeforeRender;
isOnBeforeRenderSubscribed = false;
}
Destroy(texture2DBlitMaterial);
CompositionLayerManager.GetInstance().UnsubscribeFromLayerManager(this, true);
}
private void LateUpdate()
{
if (isAutoFallbackActive) //Do not submit when auto fallback is active
{
//Check if auto fallback mesh needs to be updated
if (!FallbackMeshIsValid()) //fallback Mesh needs to be generated
{
AutoFallbackMeshGeneration();
}
else if (LayerDimensionsChanged()) //if fallback Mesh is present but needs to be updated
{
AutoFallbackMeshUpdate();
}
//Handle possible lossy scale change
if (generatedFallbackMesh.transform.lossyScale != Vector3.one)
{
generatedFallbackMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
}
return;
}
if (layerType == LayerType.Underlay)
{
if (!UnderlayMeshIsValid()) //Underlay Mesh needs to be generated
{
UnderlayMeshGeneration();
}
else if (LayerDimensionsChanged()) //if Underlay Mesh is present but needs to be updated
{
UnderlayMeshUpdate();
}
//Handle possible lossy scale change
if (generatedUnderlayMesh.transform.lossyScale != Vector3.one)
{
generatedUnderlayMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
}
generatedUnderlayMesh.SetActive(true);
}
}
private void OnBeforeRender()
{
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
isLayerReadyForSubmit = false;
if (compositionLayerFeature.XrSessionEnding) return;
if (!isInitializationComplete) //Layer not marked as initialized
{
if (InitStatus) //Initialized
{
reinitializationNeeded = false;
isInitializationComplete = true;
isSynchronized = false;
}
else if (reinitializationNeeded) //Layer is still active but needs to be reinitialized
{
CompositionLayerInit();
return;
}
else
{
DEBUG("Composition Layer Lifecycle OnBeforeRender: Layer not initialized.");
return;
}
}
if (!isSynchronized)
{
DEBUG("CompositionLayer: Sync");
if (synchronizer != null)
{
synchronizer.sync();
isSynchronized = true;
}
}
if (isAutoFallbackActive || ((compositionLayerPlaceholderPrefabGO != null) && compositionLayerPlaceholderPrefabGO.activeSelf)) //Do not submit when auto fallback or placeholder is active
{
return;
}
if (compositionLayerFeature.XrSessionCurrentState != XrSessionState.XR_SESSION_STATE_VISIBLE && compositionLayerFeature.XrSessionCurrentState != XrSessionState.XR_SESSION_STATE_FOCUSED)
{
//DEBUG("XrSessionCurrentState is not focused or visible");
return;
}
if (SetLayerTexture())
{
isLayerReadyForSubmit = true;
}
if (!isLayerReadyForSubmit)
{
DEBUG("Composition Layer Lifecycle OnBeforeRender: Layer not ready for submit.");
return;
}
SubmitCompositionLayer();
isLayerReadyForSubmit = false; //reset flag after submit
}
#endregion
#region Mesh Generation
public void AutoFallbackMeshGeneration()
{
if (generatedFallbackMeshFilter != null && generatedFallbackMeshFilter.mesh != null)
{
Destroy(generatedFallbackMeshFilter.mesh);
}
if (generatedFallbackMeshRenderer != null && generatedFallbackMeshRenderer.material != null)
{
Destroy(generatedFallbackMeshRenderer.material);
}
if (generatedFallbackMesh != null) Destroy(generatedFallbackMesh);
Mesh generatedMesh = null;
switch (layerShape)
{
case LayerShape.Quad:
generatedMesh = MeshGenerationHelper.GenerateQuadMesh(MeshGenerationHelper.GenerateQuadVertex(m_QuadWidth, m_QuadHeight));
break;
case LayerShape.Cylinder:
generatedMesh = MeshGenerationHelper.GenerateCylinderMesh(m_CylinderAngleOfArc, MeshGenerationHelper.GenerateCylinderVertex(m_CylinderAngleOfArc, m_CylinderRadius, m_CylinderHeight));
break;
}
generatedFallbackMesh = new GameObject();
generatedFallbackMesh.SetActive(false);
generatedFallbackMesh.name = FallbackMeshName;
generatedFallbackMesh.transform.SetParent(gameObject.transform);
generatedFallbackMesh.transform.localPosition = Vector3.zero;
generatedFallbackMesh.transform.localRotation = Quaternion.identity;
generatedFallbackMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
generatedFallbackMeshRenderer = generatedFallbackMesh.AddComponent<MeshRenderer>();
generatedFallbackMeshFilter = generatedFallbackMesh.AddComponent<MeshFilter>();
generatedFallbackMeshFilter.mesh = generatedMesh;
Material fallBackMat = new Material(Shader.Find("Unlit/Transparent"));
fallBackMat.mainTexture = texture;
generatedFallbackMeshRenderer.material = fallBackMat;
}
public void AutoFallbackMeshUpdate()
{
if (generatedFallbackMesh == null || generatedFallbackMeshRenderer == null || generatedFallbackMeshFilter == null)
{
return;
}
Mesh generatedMesh = null;
switch (layerShape)
{
case LayerShape.Quad:
generatedMesh = MeshGenerationHelper.GenerateQuadMesh(MeshGenerationHelper.GenerateQuadVertex(m_QuadWidth, m_QuadHeight));
break;
case LayerShape.Cylinder:
generatedMesh = MeshGenerationHelper.GenerateCylinderMesh(m_CylinderAngleOfArc, MeshGenerationHelper.GenerateCylinderVertex(m_CylinderAngleOfArc, m_CylinderRadius, m_CylinderHeight));
break;
}
generatedFallbackMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
Destroy(generatedFallbackMeshFilter.mesh);
generatedFallbackMeshFilter.mesh = generatedMesh;
generatedFallbackMeshRenderer.material.mainTexture = texture;
}
public bool FallbackMeshIsValid()
{
if (generatedFallbackMesh == null || generatedFallbackMeshRenderer == null || generatedFallbackMeshFilter == null)
{
return false;
}
else if (generatedFallbackMeshFilter.mesh == null || generatedFallbackMeshRenderer.material == null)
{
return false;
}
return true;
}
public void UnderlayMeshGeneration()
{
if (generatedUnderlayMeshFilter != null && generatedUnderlayMeshFilter.mesh != null)
{
Destroy(generatedUnderlayMeshFilter.mesh);
}
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
{
Destroy(generatedUnderlayMeshRenderer.material);
}
if (generatedUnderlayMesh != null) Destroy(generatedUnderlayMesh);
switch (layerShape)
{
case LayerShape.Cylinder:
//Generate vertices
Vector3[] cylinderVertices = MeshGenerationHelper.GenerateCylinderVertex(m_CylinderAngleOfArc, m_CylinderRadius, m_CylinderHeight);
//Add components to Game Object
generatedUnderlayMesh = new GameObject();
generatedUnderlayMesh.name = CylinderUnderlayMeshName;
generatedUnderlayMesh.transform.SetParent(transform);
generatedUnderlayMesh.transform.localPosition = Vector3.zero;
generatedUnderlayMesh.transform.localRotation = Quaternion.identity;
generatedUnderlayMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
generatedUnderlayMeshRenderer = generatedUnderlayMesh.AddComponent<MeshRenderer>();
generatedUnderlayMeshFilter = generatedUnderlayMesh.AddComponent<MeshFilter>();
generatedUnderlayMeshRenderer.sharedMaterial = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"));
generatedUnderlayMeshRenderer.material.mainTexture = texture;
//Generate Mesh
generatedUnderlayMeshFilter.mesh = MeshGenerationHelper.GenerateCylinderMesh(m_CylinderAngleOfArc, cylinderVertices);
break;
case LayerShape.Quad:
default:
//Generate vertices
Vector3[] quadVertices = MeshGenerationHelper.GenerateQuadVertex(m_QuadWidth, m_QuadHeight);
//Add components to Game Object
generatedUnderlayMesh = new GameObject();
generatedUnderlayMesh.name = QuadUnderlayMeshName;
generatedUnderlayMesh.transform.SetParent(transform);
generatedUnderlayMesh.transform.localPosition = Vector3.zero;
generatedUnderlayMesh.transform.localRotation = Quaternion.identity;
generatedUnderlayMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
generatedUnderlayMeshRenderer = generatedUnderlayMesh.AddComponent<MeshRenderer>();
generatedUnderlayMeshFilter = generatedUnderlayMesh.AddComponent<MeshFilter>();
generatedUnderlayMeshRenderer.material = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"));
generatedUnderlayMeshRenderer.material.mainTexture = texture;
//Generate Mesh
generatedUnderlayMeshFilter.mesh = MeshGenerationHelper.GenerateQuadMesh(quadVertices);
break;
}
}
public void UnderlayMeshUpdate()
{
if (generatedUnderlayMesh == null || generatedUnderlayMeshRenderer == null || generatedUnderlayMeshFilter == null)
{
return;
}
switch (layerShape)
{
case LayerShape.Cylinder:
//Generate vertices
Vector3[] cylinderVertices = MeshGenerationHelper.GenerateCylinderVertex(m_CylinderAngleOfArc, m_CylinderRadius, m_CylinderHeight);
//Generate Mesh
Destroy(generatedUnderlayMeshFilter.mesh);
generatedUnderlayMeshFilter.mesh = MeshGenerationHelper.GenerateCylinderMesh(m_CylinderAngleOfArc, cylinderVertices);
break;
case LayerShape.Quad:
default:
//Generate vertices
Vector3[] quadVertices = MeshGenerationHelper.GenerateQuadVertex(m_QuadWidth, m_QuadHeight);
//Generate Mesh
Destroy(generatedUnderlayMeshFilter.mesh);
generatedUnderlayMeshFilter.mesh = MeshGenerationHelper.GenerateQuadMesh(quadVertices);
break;
}
generatedUnderlayMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
}
public Vector3 GetNormalizedLocalScale(Transform targetTransform, Vector3 targetGlobalScale) //Return the local scale needed to make it match to the target global scale
{
Vector3 normalizedLocalScale = new Vector3(targetGlobalScale.x / targetTransform.lossyScale.x, targetGlobalScale.y / targetTransform.lossyScale.y, targetGlobalScale.z / targetTransform.lossyScale.z);
return normalizedLocalScale;
}
public bool UnderlayMeshIsValid()
{
if (generatedUnderlayMesh == null || generatedUnderlayMeshRenderer == null || generatedUnderlayMeshFilter == null)
{
return false;
}
else if (generatedUnderlayMeshFilter.mesh == null || generatedUnderlayMeshRenderer.material == null)
{
return false;
}
return true;
}
#endregion
#region Enum Definitions
public enum LayerType
{
Overlay = 1,
Underlay = 2,
}
public enum LayerShape
{
Quad = 0,
Cylinder = 1,
}
public enum Visibility
{
Both = 0,
Left = 1,
Right = 2,
}
#if UNITY_EDITOR
public enum CylinderLayerParamAdjustmentMode
{
Radius = 0,
ArcLength = 1,
ArcAngle = 2,
}
public enum CylinderLayerParamLockMode
{
ArcLength = 0,
ArcAngle = 1,
}
#endif
public enum BlitShaderMode
{
LINEAR_TO_SRGB_COLOR = 0,
LINEAR_TO_SRGB_ALPHA = 1,
}
#endregion
#region Helper Classes
private class LayerTextures
{
private uint layerTextureQueueLength;
public uint currentAvailableTextureIndex { get; set; }
public IntPtr[] textureIDs;
public Texture[] externalTextures;
public bool[] textureContentSet;
public XrRect2Di textureLayout { get; set; }
public LayerTextures(uint swapchainImageCount)
{
layerTextureQueueLength = swapchainImageCount;
textureIDs = new IntPtr[swapchainImageCount];
externalTextures = new Texture[swapchainImageCount];
textureContentSet = new bool[swapchainImageCount];
for (int i = 0; i < swapchainImageCount; i++)
{
textureContentSet[i] = false;
textureIDs[i] = IntPtr.Zero;
}
}
public IntPtr GetCurrentAvailableTextureID()
{
if (currentAvailableTextureIndex < 0 || currentAvailableTextureIndex > layerTextureQueueLength - 1)
{
return IntPtr.Zero;
}
return textureIDs[currentAvailableTextureIndex];
}
public void SetCurrentAvailableTextureID(IntPtr newTextureID)
{
if (currentAvailableTextureIndex < 0 || currentAvailableTextureIndex > layerTextureQueueLength - 1)
{
return;
}
textureIDs[currentAvailableTextureIndex] = newTextureID;
}
public Texture GetCurrentAvailableExternalTexture()
{
if (currentAvailableTextureIndex < 0 || currentAvailableTextureIndex > layerTextureQueueLength - 1)
{
return null;
}
return externalTextures[currentAvailableTextureIndex];
}
public void SetCurrentAvailableExternalTexture(Texture newExternalTexture)
{
if (currentAvailableTextureIndex < 0 || currentAvailableTextureIndex > layerTextureQueueLength - 1)
{
return;
}
externalTextures[currentAvailableTextureIndex] = newExternalTexture;
}
}
private class CompositionLayerRenderThreadTask : Task
{
public int layerID;
public CompositionLayerRenderThreadTask() { }
public static void IssueObtainSwapchainEvent(CompositionLayerRenderThreadSyncObject syncObject)
{
PreAllocatedQueue taskQueue = syncObject.Queue;
lock (taskQueue)
{
CompositionLayerRenderThreadTask task = taskQueue.Obtain<CompositionLayerRenderThreadTask>();
taskQueue.Enqueue(task);
}
syncObject.IssueEvent();
}
public static void IssueDestroySwapchainEvent(CompositionLayerRenderThreadSyncObject syncObject, int inLayerID)
{
PreAllocatedQueue taskQueue = syncObject.Queue;
lock (taskQueue)
{
CompositionLayerRenderThreadTask task = taskQueue.Obtain<CompositionLayerRenderThreadTask>();
task.layerID = inLayerID;
taskQueue.Enqueue(task);
}
syncObject.IssueEvent();
}
}
private class RenderThreadSynchronizer
{
RenderTexture mutable = new RenderTexture(1, 1, 0);
public RenderThreadSynchronizer()
{
mutable.useMipMap = false;
mutable.Create();
}
public void sync()
{
var originalLogType = Application.GetStackTraceLogType(LogType.Error);
Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.None);
// Sync
mutable.GetNativeTexturePtr();
Application.SetStackTraceLogType(LogType.Error, originalLogType);
}
}
private class UnityToOpenXRConversionHelper
{
public static void GetOpenXRQuaternion(Quaternion rot, ref XrQuaternionf qua)
{
qua.x = rot.x;
qua.y = rot.y;
qua.z = -rot.z;
qua.w = -rot.w;
}
public static void GetOpenXRVector(Vector3 pos, ref XrVector3f vec)
{
vec.x = pos.x;
vec.y = pos.y;
vec.z = -pos.z;
}
public static void GetOpenXRColor4f(Color color, ref XrColor4f color4f)
{
color4f.r = color.r;
color4f.g = color.g;
color4f.b = color.b;
color4f.a = color.a;
}
}
public static class MeshGenerationHelper
{
public static Vector3[] GenerateQuadVertex(float quadWidth, float quadHeight)
{
Vector3[] vertices = new Vector3[4]; //Four corners
vertices[0] = new Vector3(-quadWidth / 2, -quadHeight / 2, 0); //Bottom Left
vertices[1] = new Vector3(quadWidth / 2, -quadHeight / 2, 0); //Bottom Right
vertices[2] = new Vector3(-quadWidth / 2, quadHeight / 2, 0); //Top Left
vertices[3] = new Vector3(quadWidth / 2, quadHeight / 2, 0); //Top Right
return vertices;
}
public static Mesh GenerateQuadMesh(Vector3[] vertices)
{
Mesh quadMesh = new Mesh();
quadMesh.vertices = vertices;
//Create array that represents vertices of the triangles
int[] triangles = new int[6];
triangles[0] = 0;
triangles[1] = 2;
triangles[2] = 1;
triangles[3] = 1;
triangles[4] = 2;
triangles[5] = 3;
quadMesh.triangles = triangles;
Vector2[] uv = new Vector2[vertices.Length];
Vector4[] tangents = new Vector4[vertices.Length];
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
for (int i = 0, y = 0; y < 2; y++)
{
for (int x = 0; x < 2; x++, i++)
{
uv[i] = new Vector2((float)x, (float)y);
tangents[i] = tangent;
}
}
quadMesh.uv = uv;
quadMesh.tangents = tangents;
quadMesh.RecalculateNormals();
return quadMesh;
}
public static Vector3[] GenerateCylinderVertex(float cylinderAngleOfArc, float cylinderRadius, float cylinderHeight)
{
float angleUpperLimitDeg = cylinderAngleOfArc / 2; //Degrees
float angleLowerLimitDeg = -angleUpperLimitDeg; //Degrees
float angleUpperLimitRad = angleUpperLimitDeg * Mathf.Deg2Rad; //Radians
float angleLowerLimitRad = angleLowerLimitDeg * Mathf.Deg2Rad; //Radians
int arcSegments = Mathf.RoundToInt(cylinderAngleOfArc / 5f);
float anglePerArcSegmentRad = (cylinderAngleOfArc / arcSegments) * Mathf.Deg2Rad;
Vector3[] vertices = new Vector3[2 * (arcSegments + 1)]; //Top and bottom lines * Vertex count per line
int vertexCount = 0;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < arcSegments + 1; j++) //Clockwise
{
float currentVertexAngleRad = angleLowerLimitRad + anglePerArcSegmentRad * j;
float x = cylinderRadius * Mathf.Sin(currentVertexAngleRad);
float y = 0;
float z = cylinderRadius * Mathf.Cos(currentVertexAngleRad);
if (i == 1) //Top
{
y += cylinderHeight / 2;
}
else //Bottom
{
y -= cylinderHeight / 2;
}
vertices[vertexCount] = new Vector3(x, y, z);
vertexCount++;
}
}
return vertices;
}
public static Mesh GenerateCylinderMesh(float cylinderAngleOfArc, Vector3[] vertices)
{
Mesh cylinderMesh = new Mesh();
cylinderMesh.vertices = vertices;
int arcSegments = Mathf.RoundToInt(cylinderAngleOfArc / 5f);
//Create array that represents vertices of the triangles
int[] triangles = new int[arcSegments * 6];
for (int currentTriangleIndex = 0, currentVertexIndex = 0, y = 0; y < 1; y++, currentVertexIndex++)
{
for (int x = 0; x < arcSegments; x++, currentTriangleIndex += 6, currentVertexIndex++)
{
triangles[currentTriangleIndex] = currentVertexIndex;
triangles[currentTriangleIndex + 1] = currentVertexIndex + arcSegments + 1;
triangles[currentTriangleIndex + 2] = currentVertexIndex + 1;
triangles[currentTriangleIndex + 3] = currentVertexIndex + 1;
triangles[currentTriangleIndex + 4] = currentVertexIndex + arcSegments + 1;
triangles[currentTriangleIndex + 5] = currentVertexIndex + arcSegments + 2;
}
}
cylinderMesh.triangles = triangles;
Vector2[] uv = new Vector2[vertices.Length];
Vector4[] tangents = new Vector4[vertices.Length];
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
for (int i = 0, y = 0; y < 2; y++)
{
for (int x = 0; x < arcSegments + 1; x++, i++)
{
uv[i] = new Vector2((float)x / arcSegments, (float)y);
tangents[i] = tangent;
}
}
cylinderMesh.uv = uv;
cylinderMesh.tangents = tangents;
cylinderMesh.RecalculateNormals();
return cylinderMesh;
}
}
public static class CylinderParameterHelper
{
public static float RadiusAndDegAngleOfArcToArcLength(float inDegAngleOfArc, float inRadius)
{
float arcLength = inRadius * (inDegAngleOfArc * Mathf.Deg2Rad);
return arcLength;
}
public static float RadiusAndArcLengthToDegAngleOfArc(float inArcLength, float inRadius)
{
float degAngleOfArc = (inArcLength / inRadius) * Mathf.Rad2Deg;
return degAngleOfArc;
}
public static float ArcLengthAndDegAngleOfArcToRadius(float inArcLength, float inDegAngleOfArc)
{
float radius = (inArcLength / (inDegAngleOfArc * Mathf.Deg2Rad));
return radius;
}
}
#endregion
}
}