Files
VIVE-OpenXR-Unity/com.htc.upm.vive.openxr/Runtime/CompositionLayer/Scripts/CompositionLayer.cs
2024-01-10 14:20:05 +08:00

1845 lines
56 KiB
C#

// Copyright HTC Corporation All Rights Reserved.
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
}
}