diff --git a/Assets/LIV/Resources/Shaders/LIV_ClipPlaneComplex.shader b/Assets/LIV/Resources/Shaders/LIV_ClipPlaneComplex.shader new file mode 100644 index 0000000..14a6cae --- /dev/null +++ b/Assets/LIV/Resources/Shaders/LIV_ClipPlaneComplex.shader @@ -0,0 +1,90 @@ +Shader "Hidden/LIV_ClipPlaneComplex" +{ + Properties{ + _LivClipPlaneHeightMap("Clip Plane Height Map", 2D) = "black" {} + } + + SubShader + { + Tags { "Queue" = "Overlay" "LightMode" = "Always" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"} + + Pass { + Name "CLIP_PLANE_COMPLEX" + Cull Off + ZWrite On + Blend Off + Fog{ Mode Off } + ColorMask[_LivColorMask] + + CGPROGRAM + + #pragma target 4.6 + + #pragma vertex TessellationVertexProgram + #pragma fragment FragmentProgram + #pragma hull HullProgram + #pragma domain DomainProgram + + #include "UnityCG.cginc" + + sampler2D _LivClipPlaneHeightMap; + float _LivTessellation; + + struct VertexData { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct TessellationFactors { + float edge[4] : SV_TessFactor; + float inside[2] : SV_InsideTessFactor; + }; + + struct TessellationControlPoint { + float4 vertex : INTERNALTESSPOS; + float2 uv : TEXCOORD0; + }; + + [domain("quad")] + [outputcontrolpoints(4)] + [outputtopology("triangle_cw")] + [partitioning("fractional_odd")] + [patchconstantfunc("PatchConstantFunction")] + TessellationControlPoint HullProgram(InputPatch patch, uint id : SV_OutputControlPointID) { + return patch[id]; + } + + TessellationFactors PatchConstantFunction(InputPatch patch) { + TessellationFactors f; + float t = _LivTessellation; + f.edge[0] = f.edge[1] = f.edge[2] = f.edge[3] = f.inside[0] = f.inside[1] = t; + return f; + } + + [domain("quad")] + VertexData DomainProgram(TessellationFactors factors, OutputPatch patch, float2 uv : SV_DomainLocation) { + VertexData data; + + data.uv = lerp(lerp(patch[0].uv, patch[1].uv, uv.x), lerp(patch[3].uv, patch[2].uv, uv.x), uv.y); + float4 vertex = lerp(lerp(patch[0].vertex, patch[1].vertex, uv.x), lerp(patch[3].vertex, patch[2].vertex, uv.x), uv.y); + vertex.z += tex2Dlod(_LivClipPlaneHeightMap, float4(data.uv, 0, 0)).r; + data.vertex = UnityObjectToClipPos(vertex); + + return data; + } + + TessellationControlPoint TessellationVertexProgram(VertexData v) { + TessellationControlPoint p; + p.vertex = v.vertex; + p.uv = v.uv; + return p; + } + + fixed4 FragmentProgram(TessellationControlPoint i) : SV_Target{ + return fixed4(0, 0, 0, 0); + } + + ENDCG + } + } +} diff --git a/Assets/LIV/Resources/Shaders/LIV_ClipPlaneComplexDebug.shader b/Assets/LIV/Resources/Shaders/LIV_ClipPlaneComplexDebug.shader new file mode 100644 index 0000000..498753b --- /dev/null +++ b/Assets/LIV/Resources/Shaders/LIV_ClipPlaneComplexDebug.shader @@ -0,0 +1,120 @@ +Shader "Hidden/LIV_ClipPlaneComplexDebug" +{ + Properties{ + _LivClipPlaneHeightMap("Clip Plane Height Map", 2D) = "black" {} + } + + SubShader + { + Tags { "Queue" = "Background" "LightMode" = "Always" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"} + + Pass { + Name "CLIP_PLANE_COMPLEX_DEBUG" + Cull Off ZWrite On + Fog{ Mode Off } + + CGPROGRAM + + #pragma target 4.6 + + #pragma vertex TessellationVertexProgram + #pragma fragment FragmentProgram + #pragma hull HullProgram + #pragma domain DomainProgram + #pragma geometry GeometryProgram + + #include "UnityCG.cginc" + + sampler2D _LivClipPlaneHeightMap; + float _LivTessellation; + + struct VertexData { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + float4 worldPos : TEXCOORD1; + }; + + struct GeomToFragData { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + float3 barycentric : TEXCOORD1; + }; + + struct TessellationFactors { + float edge[4] : SV_TessFactor; + float inside[2] : SV_InsideTessFactor; + }; + + struct TessellationControlPoint { + float4 vertex : INTERNALTESSPOS; + float2 uv : TEXCOORD0; + }; + + [domain("quad")] + [outputcontrolpoints(4)] + [outputtopology("triangle_cw")] + [partitioning("fractional_odd")] + [patchconstantfunc("PatchConstantFunction")] + TessellationControlPoint HullProgram(InputPatch patch, uint id : SV_OutputControlPointID) { + return patch[id]; + } + + TessellationFactors PatchConstantFunction(InputPatch patch) { + TessellationFactors f; + float t = _LivTessellation; + f.edge[0] = f.edge[1] = f.edge[2] = f.edge[3] = f.inside[0] = f.inside[1] = t; + return f; + } + + [domain("quad")] + VertexData DomainProgram(TessellationFactors factors, OutputPatch patch, float2 uv : SV_DomainLocation) { + VertexData data; + + data.uv = lerp(lerp(patch[0].uv, patch[1].uv, uv.x), lerp(patch[3].uv, patch[2].uv, uv.x), uv.y); + float4 vertex = lerp(lerp(patch[0].vertex, patch[1].vertex, uv.x), lerp(patch[3].vertex, patch[2].vertex, uv.x), uv.y); + vertex.z += tex2Dlod(_LivClipPlaneHeightMap, float4(data.uv, 0, 0)).r; + data.vertex = UnityObjectToClipPos(vertex); + data.worldPos = mul(unity_ObjectToWorld, vertex); + + return data; + } + + TessellationControlPoint TessellationVertexProgram(VertexData v) { + TessellationControlPoint p; + p.vertex = v.vertex; + p.uv = v.uv; + return p; + } + + [maxvertexcount(3)] + void GeometryProgram(triangle VertexData p[3], inout TriangleStream triStream) { + GeomToFragData pIn; + + pIn.vertex = p[0].vertex; + pIn.uv = p[0].uv; + pIn.barycentric.xyz = float3(1.0, 0, 0); + triStream.Append(pIn); + + pIn.vertex = p[1].vertex; + pIn.uv = p[1].uv; + pIn.barycentric.xyz = float3(0, 1.0, 0); + triStream.Append(pIn); + + pIn.vertex = p[2].vertex; + pIn.uv = p[2].uv; + pIn.barycentric.xyz = float3(0, 0, 1.0); + triStream.Append(pIn); + } + + fixed4 FragmentProgram(GeomToFragData i) : SV_Target { + float3 barys; + barys.xy = i.barycentric; + barys.z = 1 - barys.x - barys.y; + barys = smoothstep(0.0, 0.0 + fwidth(barys), barys); + return lerp(float4(0.0, 0.0, 0.0, 0.5), float4(0.0, 1.0, 0.0, 0.5), min(barys.x, min(barys.y, barys.z))); + } + + ENDCG + } + } +} diff --git a/Assets/LIV/Resources/Shaders/LIV_ClipPlaneSimple.shader b/Assets/LIV/Resources/Shaders/LIV_ClipPlaneSimple.shader new file mode 100644 index 0000000..f301c2b --- /dev/null +++ b/Assets/LIV/Resources/Shaders/LIV_ClipPlaneSimple.shader @@ -0,0 +1,47 @@ +Shader "Hidden/LIV_ClipPlaneSimple" +{ + SubShader + { + Tags { "Queue" = "Overlay" "LightMode" = "Always" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"} + + Pass { + Name "CLIP_PLANE_SIMPLE" + Cull Off + ZWrite On + Blend Off + Fog{ Mode Off } + ColorMask[_LivColorMask] + + CGPROGRAM + + #pragma target 4.6 + + #pragma vertex VertexProgram + #pragma fragment FragmentProgram + + #include "UnityCG.cginc" + + struct VertexData { + float4 vertex : POSITION; + }; + + struct VertexToFragData { + float4 vertex : POSITION; + }; + + VertexToFragData VertexProgram(VertexData v) + { + VertexToFragData o; + o.vertex = UnityObjectToClipPos(v.vertex); + return o; + } + + fixed4 FragmentProgram(VertexToFragData i) : SV_Target + { + return fixed4(0, 0, 0, 0); + } + + ENDCG + } + } +} diff --git a/Assets/LIV/Resources/Shaders/LIV_ClipPlaneSimpleDebug.shader b/Assets/LIV/Resources/Shaders/LIV_ClipPlaneSimpleDebug.shader new file mode 100644 index 0000000..29a430a --- /dev/null +++ b/Assets/LIV/Resources/Shaders/LIV_ClipPlaneSimpleDebug.shader @@ -0,0 +1,79 @@ +Shader "Hidden/LIV_ClipPlaneSimpleDebug" +{ + SubShader + { + Tags { "Queue" = "Overlay" "LightMode" = "Always" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"} + + Pass { + Name "CLIP_PLANE_SIMPLE_DEBUG" + Cull Off + ZWrite On + Fog{ Mode Off } + + CGPROGRAM + + #pragma target 4.6 + + #pragma vertex VertexProgram + #pragma fragment FragmentProgram + #pragma geometry GeometryProgram + + #include "UnityCG.cginc" + + struct VertexData { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct VertexToGeomData { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct GeomToFragData { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + float3 barycentric : TEXCOORD1; + }; + + VertexToGeomData VertexProgram(VertexData v) + { + VertexToGeomData o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + [maxvertexcount(3)] + void GeometryProgram(triangle VertexToGeomData p[3], inout TriangleStream triStream) { + GeomToFragData pIn; + + pIn.vertex = p[0].vertex; + pIn.uv = p[0].uv; + pIn.barycentric.xyz = float3(1.0, 0, 0); + triStream.Append(pIn); + + pIn.vertex = p[1].vertex; + pIn.uv = p[1].uv; + pIn.barycentric.xyz = float3(0, 1.0, 0); + triStream.Append(pIn); + + pIn.vertex = p[2].vertex; + pIn.uv = p[2].uv; + pIn.barycentric.xyz = float3(0, 0, 1.0); + triStream.Append(pIn); + } + + fixed4 FragmentProgram(GeomToFragData i) : SV_Target + { + float3 barys; + barys.xy = i.barycentric; + barys.z = 1 - barys.x - barys.y; + barys = smoothstep(0.0, 0.0 + fwidth(barys), barys); + return lerp(float4(0.0, 0.0, 0.0, 0.5), float4(0.0, 1.0, 0.0, 0.5), min(barys.x, min(barys.y, barys.z))); + } + + ENDCG + } + } +} diff --git a/Assets/LIV/Resources/Shaders/LIV_CombineAlpha.shader b/Assets/LIV/Resources/Shaders/LIV_CombineAlpha.shader new file mode 100644 index 0000000..fc8a872 --- /dev/null +++ b/Assets/LIV/Resources/Shaders/LIV_CombineAlpha.shader @@ -0,0 +1,58 @@ +Shader "Hidden/LIV_CombineAlpha" +{ + Properties + { + _MainTex ("Texture", 2D) = "black" {} + } + + SubShader + { + Tags { "Queue" = "Overlay" "LightMode" = "Always" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"} + + Pass + { + Name "COMBINE_ALPHA" + Blend One OneMinusSrcAlpha + Ztest Always + Zwrite Off + Cull Off + ColorMask[_LivColorMask] + Fog{ Mode Off } + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + sampler2D _MainTex; + float4 _MainTex_ST; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = TRANSFORM_TEX(v.uv, _MainTex); + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + return tex2D(_MainTex, i.uv).a; + } + ENDCG + } + } +} diff --git a/Assets/LIV/Resources/Shaders/LIV_ForceForwardRendering.shader b/Assets/LIV/Resources/Shaders/LIV_ForceForwardRendering.shader new file mode 100644 index 0000000..81ad111 --- /dev/null +++ b/Assets/LIV/Resources/Shaders/LIV_ForceForwardRendering.shader @@ -0,0 +1,47 @@ +Shader "Hidden/LIV_ForceForwardRendering" +{ + SubShader + { + Tags { "Queue" = "Geometry" "LightMode" = "Always" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"} + + Pass + { + Name "FORCE_FORWARD_RENDERING" + ZTest Always ZWrite Off ColorMask 0 + Fog{ Mode Off } + + CGPROGRAM + + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + }; + + sampler2D _MainTex; + + v2f vert(appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + return o; + } + + fixed4 frag(v2f i) : SV_Target + { + return 1; + } + + ENDCG + } + } +} diff --git a/Assets/LIV/Resources/Shaders/LIV_Write.shader b/Assets/LIV/Resources/Shaders/LIV_Write.shader new file mode 100644 index 0000000..0dfaea9 --- /dev/null +++ b/Assets/LIV/Resources/Shaders/LIV_Write.shader @@ -0,0 +1,55 @@ +Shader "Hidden/LIV_Write" +{ + Properties + { + _MainTex("Texture", 2D) = "black" {} + } + SubShader + { + Tags { "Queue" = "Background" "LightMode" = "Always" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"} + + Pass + { + Name "WRITE" + ZTest Always ZWrite Off + ColorMask [_LivColorMask] + Fog{ Mode Off } + + CGPROGRAM + + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + sampler2D _MainTex; + + v2f vert(appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + fixed4 frag(v2f i) : SV_Target + { + return tex2D(_MainTex, i.uv); + } + + ENDCG + } + } +} diff --git a/Assets/LIV/Resources/Shaders/LIV_WriteOpaqueToAlpha.shader b/Assets/LIV/Resources/Shaders/LIV_WriteOpaqueToAlpha.shader new file mode 100644 index 0000000..b57a5ab --- /dev/null +++ b/Assets/LIV/Resources/Shaders/LIV_WriteOpaqueToAlpha.shader @@ -0,0 +1,47 @@ +Shader "Hidden/LIV_WriteOpaqueToAlpha" +{ + SubShader + { + Tags { "Queue" = "Overlay" "LightMode" = "Always" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"} + + Pass { + Name "CLIP_PLANE_FIX_ALPHA" + Blend Off + ZTest Greater + ZWrite Off + Cull Off + ColorMask A + Fog{ Mode Off } + + CGPROGRAM + + #pragma target 4.6 + + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct v2f + { + float4 vertex : SV_POSITION; + }; + + v2f vert(appdata_base v) + { + v2f o; + o.vertex.xy = v.vertex.xy * 2.0; + o.vertex.z = 0; + o.vertex.w = 1; + return o; + } + + fixed4 frag(v2f i) : SV_Target + { + return 1; + } + + ENDCG + } + } +} diff --git a/Assets/LIV/Scripts/Editor/LIVEditor.cs b/Assets/LIV/Scripts/Editor/LIVEditor.cs new file mode 100644 index 0000000..4732dd6 --- /dev/null +++ b/Assets/LIV/Scripts/Editor/LIVEditor.cs @@ -0,0 +1,278 @@ +using UnityEngine; +using UnityEditor; +using UnityEditorInternal; + +namespace LIV.SDK.Unity +{ + [CustomEditor(typeof(LIV))] + public class LIVEditor : Editor + { + const string EXCLUDE_BEHAVIOURS_FOLDOUT_KEY = "liv_excludeBehavioursfoldout"; + + const string STAGE_PROPERTY = "_stage"; + const string STAGE_TRANSFORM_PROPERTY = "_stageTransform"; + const string HMD_CAMERA_PROPERTY = "_HMDCamera"; + const string MR_CAMERA_PREFAB_PROPERTY = "_MRCameraPrefab"; + const string DISABLE_STANDARD_ASSETS_PROPERTY = "_disableStandardAssets"; + const string SPECTATOR_LAYER_MASK_PROPERTY = "_spectatorLayerMask"; + const string EXCLUDE_BEHAVIOURS_PROPERTY = "_excludeBehaviours"; + const string FIX_POST_EFFECTS_ALPHA_PROPERTY = "_fixPostEffectsAlpha"; + + static GUIContent REQUIRED_FOLDOUT_GUICONTENT = new GUIContent("Required"); + static GUIContent OPTIONAL_FOLDOUT_GUICONTENT = new GUIContent("Optional"); + + static GUIContent STAGE_GUICONTENT = new GUIContent("Stage"); + static GUIContent STAGE_INFO_GUICONTENT = new GUIContent( + "The origin of tracked space.\n" + + "This is the object that you use to move the player around." + ); + + static GUIContent STAGE_TRANSFORM_GUICONTENT = new GUIContent("Stage Transform"); + static GUIContent STAGE_TRANSFORM_INFO_GUICONTENT = new GUIContent( + "This transform defines the stage transform." + ); + + static GUIContent HMD_CAMERA_GUICONTENT = new GUIContent("HMD Camera"); + static GUIContent HMD_CAMERA_INFO_GUICONTENT = new GUIContent( + "Set this to the camera used to render the HMD's point of view." + ); + + static GUIContent MR_CAMERA_PREFAB_GUICONTENT = new GUIContent("MR Camera Prefab"); + static GUIContent MR_CAMERA_PREFAB_INFO_GUICONTENT = new GUIContent( + "Set custom camera prefab." + ); + + static GUIContent DISABLE_STANDARD_ASSETS_GUICONTENT = new GUIContent("Disable Standard Assets"); + static GUIContent DISABLE_STANDARD_INFO_ASSETS_GUICONTENT = new GUIContent( + "If you're using Unity's standard effects and they're interfering with MR rendering, check this box." + ); + + static GUIContent SPECTATOR_LAYER_MASK_GUICONTENT = new GUIContent("Spectator Layer Mask"); + static GUIContent SPECTATOR_LAYER_MASK_INFO_GUICONTENT = new GUIContent( + "By default, we'll show everything on the spectator camera. If you want to disable certain objects from showing, update this mask property." + ); + + static GUIContent FIX_POST_EFFECTS_ALPHA_GUICONTENT = new GUIContent("Fix Post-Effects alpha channel"); + static GUIContent FIX_POST_EFFECTS_ALPHA_INFO_GUICONTENT = new GUIContent( + "Some post-effects corrupt the alpha channel, this fix tries to recover it." + ); + + + static GUIContent EXCLUDE_BEHAVIOURS_GUICONTENT = new GUIContent("Exclude Behaviours"); + + static GUIStyle VERSION_STYLE { + get { + GUIStyle g = new GUIStyle(EditorStyles.label); + g.alignment = TextAnchor.LowerLeft; + g.normal.textColor = Color.white; + g.fontStyle = FontStyle.Bold; + return g; + } + } + + static GUIStyle LIV_BUTTON_STYLE { + get { + GUIStyle g = new GUIStyle(); + return g; + } + } + + static Color darkBGColor { + get { + if (EditorGUIUtility.isProSkin) + { + return new Color(0f, 0f, 0f, 0.5f); + } else + { + return new Color(1f, 1f, 1f, 0.5f); + } + } + } + static Color lightRedBGColor { + get { + return new Color(1f, 0.5f, 0.5f, 1f); + } + } + static Color lightBGColor { + get { + return new Color(1f, 1, 1, 1f); + } + } + + ReorderableList excludeBehavioursList; + + static bool excludeBehavioursFoldoutValue { + get { + if (!EditorPrefs.HasKey(EXCLUDE_BEHAVIOURS_FOLDOUT_KEY)) return false; + return EditorPrefs.GetBool(EXCLUDE_BEHAVIOURS_FOLDOUT_KEY); + } + set { + EditorPrefs.SetBool(EXCLUDE_BEHAVIOURS_FOLDOUT_KEY, value); + } + } + + SerializedProperty stageProperty = null; + SerializedProperty stageTransformProperty = null; + SerializedProperty hmdCameraProperty = null; + SerializedProperty mrCameraPrefabProperty = null; + SerializedProperty disableStandardAssetsProperty = null; + SerializedProperty SpectatorLayerMaskProperty = null; + SerializedProperty ExcludeBehavioursProperty = null; + SerializedProperty FixPostEffectsAlphaProperty = null; + + static Texture2D _livLogo; + void OnEnable() + { + string[] livLogoGUID = AssetDatabase.FindAssets("LIVLogo t:texture2D"); + if (livLogoGUID.Length > 0) + { + _livLogo = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(livLogoGUID[0])); + } + + stageProperty = serializedObject.FindProperty(STAGE_PROPERTY); + stageTransformProperty = serializedObject.FindProperty(STAGE_TRANSFORM_PROPERTY); + hmdCameraProperty = serializedObject.FindProperty(HMD_CAMERA_PROPERTY); + mrCameraPrefabProperty = serializedObject.FindProperty(MR_CAMERA_PREFAB_PROPERTY); + disableStandardAssetsProperty = serializedObject.FindProperty(DISABLE_STANDARD_ASSETS_PROPERTY); + SpectatorLayerMaskProperty = serializedObject.FindProperty(SPECTATOR_LAYER_MASK_PROPERTY); + ExcludeBehavioursProperty = serializedObject.FindProperty(EXCLUDE_BEHAVIOURS_PROPERTY); + FixPostEffectsAlphaProperty = serializedObject.FindProperty(FIX_POST_EFFECTS_ALPHA_PROPERTY); + + excludeBehavioursList = new ReorderableList(serializedObject, ExcludeBehavioursProperty, true, true, true, true); + excludeBehavioursList.drawElementCallback = DrawListItems; + excludeBehavioursList.headerHeight = 2; + } + + void DrawListItems(Rect rect, int index, bool isActive, bool isFocused) + { + EditorGUI.PropertyField(rect, excludeBehavioursList.serializedProperty.GetArrayElementAtIndex(index), new GUIContent(""), false); + } + + void DrawList(SerializedProperty property, GUIContent guiContent) + { + EditorGUILayout.PropertyField(property, guiContent); + while (true) + { + if(!property.Next(true)) + { + break; + } + EditorGUILayout.PropertyField(property); + } + } + + void DrawProperty(SerializedProperty property, GUIContent label, GUIContent content, Color color) + { + Color lastAccentColor = GUI.color; + GUI.color = darkBGColor; + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + GUI.color = lastAccentColor; + GUILayout.Space(2); + EditorGUILayout.PropertyField(property, label); + Color lastBackgroundColor = GUI.backgroundColor; + GUI.backgroundColor = color; + EditorGUILayout.LabelField(content, EditorStyles.helpBox); + GUI.backgroundColor = lastBackgroundColor; + EditorGUILayout.EndVertical(); + } + + void RenderStageField() + { + Color color = lightBGColor; + GUIContent content = new GUIContent(STAGE_INFO_GUICONTENT); + if (stageProperty.objectReferenceValue == null) + { + color = lightRedBGColor; + content.text += "\nThe stage has to be set!"; + content.image = EditorGUIUtility.IconContent("console.erroricon").image; + } + + DrawProperty(stageProperty, STAGE_GUICONTENT, content, color); + } + + void RenderHMDField() + { + Color color = lightBGColor; + GUIContent content = new GUIContent(HMD_CAMERA_INFO_GUICONTENT); + if (hmdCameraProperty.objectReferenceValue == null) + { + color = lightRedBGColor; + content.text += "\nThe camera has to be set!"; + content.image = EditorGUIUtility.IconContent("console.erroricon").image; + } + + DrawProperty(hmdCameraProperty, HMD_CAMERA_GUICONTENT, content, color); + } + + void RenderSpectatorLayerMaskField() + { + Color color = lightBGColor; + GUIContent content = new GUIContent(SPECTATOR_LAYER_MASK_INFO_GUICONTENT); + if (SpectatorLayerMaskProperty.intValue == 0) + { + color = lightRedBGColor; + content.text += "\nAre you sure you want to render nothing?"; + content.image = EditorGUIUtility.IconContent("console.warnicon").image; + } + + DrawProperty(SpectatorLayerMaskProperty, SPECTATOR_LAYER_MASK_GUICONTENT, content, color); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + if (_livLogo != null) + { + Color lastBackgroundColor = GUI.color; + GUI.color = new Color(0f, 0f, 0f, 0.5f); + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + GUI.color = lastBackgroundColor; + Rect originalRect = EditorGUILayout.GetControlRect(GUILayout.Height(_livLogo.height)); + Rect imageRect = originalRect; + imageRect.x = (originalRect.width - _livLogo.width); + imageRect.width = _livLogo.width; + GUI.DrawTexture(imageRect, _livLogo); + GUI.Label(originalRect, "v" + SDKConstants.SDK_VERSION, VERSION_STYLE); + if(GUI.Button(originalRect, new GUIContent(), LIV_BUTTON_STYLE)) { + Application.OpenURL("https://liv.tv/"); + } + EditorGUILayout.EndVertical(); + } + + EditorGUILayout.LabelField(REQUIRED_FOLDOUT_GUICONTENT, EditorStyles.boldLabel); + RenderStageField(); + RenderHMDField(); + DrawProperty(disableStandardAssetsProperty, DISABLE_STANDARD_ASSETS_GUICONTENT, DISABLE_STANDARD_INFO_ASSETS_GUICONTENT, lightBGColor); + + RenderSpectatorLayerMaskField(); + + Color lastAccentColor = GUI.color; + GUI.color = darkBGColor; + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + GUI.color = lastAccentColor; + + EditorGUI.indentLevel++; + excludeBehavioursFoldoutValue = EditorGUILayout.Foldout(excludeBehavioursFoldoutValue, EXCLUDE_BEHAVIOURS_GUICONTENT); + EditorGUI.indentLevel--; + if (excludeBehavioursFoldoutValue) + { + excludeBehavioursList.DoLayoutList(); + } + EditorGUILayout.EndVertical(); + + EditorGUILayout.LabelField(OPTIONAL_FOLDOUT_GUICONTENT, EditorStyles.boldLabel); + DrawProperty(stageTransformProperty, STAGE_TRANSFORM_GUICONTENT, STAGE_TRANSFORM_INFO_GUICONTENT, lightBGColor); + DrawProperty(mrCameraPrefabProperty, MR_CAMERA_PREFAB_GUICONTENT, MR_CAMERA_PREFAB_INFO_GUICONTENT, lightBGColor); + DrawProperty(FixPostEffectsAlphaProperty, FIX_POST_EFFECTS_ALPHA_GUICONTENT, FIX_POST_EFFECTS_ALPHA_INFO_GUICONTENT, lightBGColor); + serializedObject.ApplyModifiedProperties(); + + GUIContent helpContent = new GUIContent(EditorGUIUtility.IconContent("_Help")); + helpContent.text = "Help"; + if (GUILayout.Button(helpContent)) + { + Application.OpenURL(@"https://liv.tv/sdk-unity-docs"); + } + EditorGUILayout.Space(); + } + } +} \ No newline at end of file diff --git a/Assets/LIV/Scripts/Editor/LIVLogo.png b/Assets/LIV/Scripts/Editor/LIVLogo.png new file mode 100644 index 0000000..bca8095 Binary files /dev/null and b/Assets/LIV/Scripts/Editor/LIVLogo.png differ diff --git a/Assets/LIV/Scripts/LIV.cs b/Assets/LIV/Scripts/LIV.cs new file mode 100644 index 0000000..8c6ea81 --- /dev/null +++ b/Assets/LIV/Scripts/LIV.cs @@ -0,0 +1,522 @@ +using UnityEngine; +using UnityEngine.Serialization; +using System.Collections; +using System; + +#if UNITY_2017_2_OR_NEWER +using UnityEngine.XR; +#endif + +namespace LIV.SDK.Unity +{ + [System.Flags] + public enum INVALIDATION_FLAGS : uint + { + NONE = 0, + HMD_CAMERA = 1, + STAGE = 2, + MR_CAMERA_PREFAB = 4, + EXCLUDE_BEHAVIOURS = 8 + } + + /// + /// The LIV SDK provides a spectator view of your application. + /// + /// + /// It contextualizes what the user feels & experiences by capturing their body directly inside your world! + /// Thanks to our software, creators can film inside your app and have full control over the camera. + /// With the power of out-of-engine compositing, a creator can express themselves freely without limits; + /// as a real person or an avatar! + /// + /// + /// + /// public class StartFromScriptExample : MonoBehaviour + /// { + /// [SerializeField] Camera _hmdCamera; + /// [SerializeField] Transform _stage; + /// [SerializeField] Transform _stageTransform; + /// [SerializeField] Camera _mrCameraPrefab; + + /// LIV.SDK.Unity.LIV _liv; + + /// private void OnEnable() + /// { + /// _liv = gameObject.AddComponent(); + /// _liv.HMDCamera = _hmdCamera; + /// _liv.stage = _stage; + /// _liv.stageTransform = _stageTransform; + /// _liv.MRCameraPrefab = _mrCameraPrefab; + /// } + + /// private void OnDisable() + /// { + /// if (_liv == null) return; + /// Destroy(_liv); + /// _liv = null; + /// } + /// } + /// + /// + [HelpURL("https://liv.tv/sdk-unity-docs")] + [AddComponentMenu("LIV/LIV")] + public class LIV : MonoBehaviour + { + /// + /// triggered when the LIV SDK is activated by the LIV App and enabled by the game. + /// + public System.Action onActivate = null; + /// + /// triggered before the Mixed Reality camera is about to render. + /// + public System.Action onPreRender = null; + /// + /// triggered before the LIV SDK starts rendering background image. + /// + public System.Action onPreRenderBackground = null; + /// + /// triggered after the LIV SDK starts rendering background image. + /// + public System.Action onPostRenderBackground = null; + /// + /// triggered before the LIV SDK starts rendering the foreground image. + /// + public System.Action onPreRenderForeground = null; + /// + /// triggered after the LIV SDK starts rendering the foreground image. + /// + public System.Action onPostRenderForeground = null; + /// + /// triggered after the Mixed Reality camera has finished rendering. + /// + public System.Action onPostRender = null; + /// + /// triggered when the LIV SDK is deactivated by the LIV App or disabled by the game. + /// + public System.Action onDeactivate = null; + + [Tooltip("This is the topmost transform of your VR rig.")] + [FormerlySerializedAs("TrackedSpaceOrigin")] + [SerializeField] Transform _stage = null; + /// + /// This is the topmost transform of your VR rig. + /// + /// + /// When implementing VR locomotion(teleporting, joystick, etc), + /// this is the GameObject that you should move around your scene. + /// It represents the centre of the user’s playspace. + /// + public Transform stage { + get { + return _stage == null ? transform.parent : _stage; + } + set { + if (value == null) + { + Debug.LogWarning("LIV: Stage cannot be null!"); + } + + if (_stage != value) + { + _stageCandidate = value; + _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE, true); + } + } + } + + [Obsolete("Use stage instead")] + public Transform trackedSpaceOrigin { + get { + return stage; + } + set { + stage = value; + } + } + + public Matrix4x4 stageLocalToWorldMatrix { + get { + return (stage != null) ? stage.localToWorldMatrix : Matrix4x4.identity; + } + } + + public Matrix4x4 stageWorldToLocalMatrix { + get { + return (stage != null) ? stage.worldToLocalMatrix : Matrix4x4.identity; + } + } + + [Tooltip("This transform is an additional wrapper to the user’s playspace.")] + [FormerlySerializedAs("StageTransform")] + [SerializeField] Transform _stageTransform = null; + /// + /// This transform is an additional wrapper to the user’s playspace. + /// + /// + /// It allows for user-controlled transformations for special camera effects & transitions. + /// If a creator is using a static camera, this transformation can give the illusion of camera movement. + /// + public Transform stageTransform { + get { + return _stageTransform; + } + set { + _stageTransform = value; + } + } + + [Tooltip("This is the camera responsible for rendering the user’s HMD.")] + [FormerlySerializedAs("HMDCamera")] + [SerializeField] Camera _HMDCamera = null; + /// + /// This is the camera responsible for rendering the user’s HMD. + /// + /// + /// The LIV SDK, by default clones this object to match your application’s rendering setup. + /// You can use your own camera prefab should you want to! + /// + public Camera HMDCamera { + get { + return _HMDCamera; + } + set { + if (value == null) + { + Debug.LogWarning("LIV: HMD Camera cannot be null!"); + } + + if (_HMDCamera != value) + { + _HMDCameraCandidate = value; + _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA, true); + } + } + } + + [Tooltip("Camera prefab for customized rendering.")] + [FormerlySerializedAs("MRCameraPrefab")] + [SerializeField] Camera _MRCameraPrefab = null; + /// + /// Camera prefab for customized rendering. + /// + /// + /// By default, LIV uses the HMD camera as a reference for the Mixed Reality camera. + /// It is cloned and set up as a Mixed Reality camera.This approach works for most apps. + /// However, some games can experience issues because of custom MonoBehaviours attached to this camera. + /// You can use a custom camera prefab for those cases. + /// + public Camera MRCameraPrefab { + get { + return _MRCameraPrefab; + } + set { + if (_MRCameraPrefab != value) + { + _MRCameraPrefabCandidate = value; + _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB, true); + } + } + } + + [Tooltip("This option disables all standard Unity assets for the Mixed Reality rendering.")] + [FormerlySerializedAs("DisableStandardAssets")] + [SerializeField] bool _disableStandardAssets = false; + /// + /// This option disables all standard Unity assets for the Mixed Reality rendering. + /// + /// + /// Unity’s standard assets can interfere with the alpha channel that LIV needs to composite MR correctly. + /// + public bool disableStandardAssets { + get { + return _disableStandardAssets; + } + set { + _disableStandardAssets = value; + } + } + + [Tooltip("The layer mask defines exactly which object layers should be rendered in MR.")] + [FormerlySerializedAs("SpectatorLayerMask")] + [SerializeField] LayerMask _spectatorLayerMask = ~0; + /// + /// The layer mask defines exactly which object layers should be rendered in MR. + /// + /// + /// You should use this to hide any in-game avatar that you’re using. + /// LIV is meant to include the user’s body for you! + /// Certain HMD-based effects should be disabled here too. + /// Also, this can be used to render special effects or additional UI only to the MR camera. + /// Useful for showing the player’s health, or current score! + /// + public LayerMask spectatorLayerMask { + get { + return _spectatorLayerMask; + } + set { + _spectatorLayerMask = value; + } + } + + [Tooltip("This is for removing unwanted scripts from the cloned MR camera.")] + [FormerlySerializedAs("ExcludeBehaviours")] + [SerializeField] + string[] _excludeBehaviours = new string[] { + "AudioListener", + "Collider", + "SteamVR_Camera", + "SteamVR_Fade", + "SteamVR_ExternalCamera" + }; + /// + /// This is for removing unwanted scripts from the cloned MR camera. + /// + /// + /// By default, we remove the AudioListener, Colliders and SteamVR scripts, as these are not necessary for rendering MR! + /// The excluded string must match the name of the MonoBehaviour. + /// + public string[] excludeBehaviours { + get { + return _excludeBehaviours; + } + set { + if (_excludeBehaviours != value) + { + _excludeBehavioursCandidate = value; + _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS, true); + } + } + } + + + /// + /// Recovers corrupted alpha channel when using post-effects. + /// + /// + [Tooltip("Recovers corrupted alpha channel when using post-effects.")] + [FormerlySerializedAs("FixPostEffectsAlpha")] + [SerializeField] + private bool _fixPostEffectsAlpha = false; + public bool fixPostEffectsAlpha { + get { + return _fixPostEffectsAlpha; + } + set { + _fixPostEffectsAlpha = value; + } + } + + /// + /// Is the curret LIV SDK setup valid. + /// + public bool isValid { + get { + if (_invalidate != INVALIDATION_FLAGS.NONE) return false; + + if (_HMDCamera == null) + { + Debug.LogError("LIV: HMD Camera is a required parameter!"); + return false; + } + + if (_stage == null) + { + Debug.LogWarning("LIV: Tracked space origin should be assigned!"); + } + + if (_spectatorLayerMask == 0) + { + Debug.LogWarning("LIV: The spectator layer mask is set to not show anything. Is this correct?"); + } + + return true; + } + } + + bool _isActive = false; + /// + /// Is the LIV SDK currently active. + /// + public bool isActive { + get { + return _isActive; + } + } + + private bool _isReady { + get { + return isValid && _enabled && SDKBridge.IsActive; + } + } + + private SDKRender _render = null; + + /// + /// Script responsible for the MR rendering. + /// + public SDKRender render { get { return _render; } } + + private bool _wasReady = false; + + private INVALIDATION_FLAGS _invalidate = INVALIDATION_FLAGS.NONE; + private Transform _stageCandidate = null; + private Camera _HMDCameraCandidate = null; + private Camera _MRCameraPrefabCandidate = null; + private string[] _excludeBehavioursCandidate = null; + + private bool _enabled = false; + private Coroutine _waitForEndOfFrameCoroutine; + + void OnEnable() + { + _enabled = true; + UpdateSDKReady(); + } + + void Update() + { + UpdateSDKReady(); + Invalidate(); + } + + void OnDisable() + { + _enabled = false; + UpdateSDKReady(); + } + + IEnumerator WaitForUnityEndOfFrame() + { + while (Application.isPlaying && enabled) + { + yield return new WaitForEndOfFrame(); + if (isActive) + { + _render.Render(); + } + } + } + + void UpdateSDKReady() + { + bool ready = _isReady; + if (ready != _wasReady) + { + OnSDKReadyChanged(ready); + _wasReady = ready; + } + } + + void OnSDKReadyChanged(bool value) + { + if (value) + { + OnSDKActivate(); + } + else + { + OnSDKDeactivate(); + } + } + + void OnSDKActivate() + { + Debug.Log("LIV: Compositor connected, setting up Mixed Reality!"); + SubmitSDKOutput(); + CreateAssets(); + StartRenderCoroutine(); + _isActive = true; + if (onActivate != null) onActivate.Invoke(); + } + + void OnSDKDeactivate() + { + Debug.Log("LIV: Compositor disconnected, cleaning up Mixed Reality."); + if (onDeactivate != null) onDeactivate.Invoke(); + StopRenderCoroutine(); + DestroyAssets(); + _isActive = false; + } + + void CreateAssets() + { + DestroyAssets(); + _render = new SDKRender(this); + } + + void DestroyAssets() + { + if (_render != null) + { + _render.Dispose(); + _render = null; + } + } + + void StartRenderCoroutine() + { + StopRenderCoroutine(); + _waitForEndOfFrameCoroutine = StartCoroutine(WaitForUnityEndOfFrame()); + } + + void StopRenderCoroutine() + { + if (_waitForEndOfFrameCoroutine != null) + { + StopCoroutine(_waitForEndOfFrameCoroutine); + _waitForEndOfFrameCoroutine = null; + } + } + + void SubmitSDKOutput() + { + SDKApplicationOutput output = SDKApplicationOutput.empty; + output.supportedFeatures = FEATURES.BACKGROUND_RENDER | + FEATURES.FOREGROUND_RENDER | + FEATURES.OVERRIDE_POST_PROCESSING | + FEATURES.FIX_FOREGROUND_ALPHA; + + output.sdkID = SDKConstants.SDK_ID; + output.sdkVersion = SDKConstants.SDK_VERSION; + output.engineName = SDKConstants.ENGINE_NAME; + output.engineVersion = Application.unityVersion; + output.applicationName = Application.productName; + output.applicationVersion = Application.version; + output.graphicsAPI = SystemInfo.graphicsDeviceType.ToString(); +#if UNITY_2017_2_OR_NEWER + output.xrDeviceName = XRSettings.loadedDeviceName; +#endif + SDKBridge.SubmitApplicationOutput(output); + } + + void Invalidate() + { + if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE)) + { + _stage = _stageCandidate; + _stageCandidate = null; + _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE, false); + } + + if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA)) + { + _HMDCamera = _HMDCameraCandidate; + _HMDCameraCandidate = null; + _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA, false); + } + + if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB)) + { + _MRCameraPrefab = _MRCameraPrefabCandidate; + _MRCameraPrefabCandidate = null; + _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB, false); + } + + if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS)) + { + _excludeBehaviours = _excludeBehavioursCandidate; + _excludeBehavioursCandidate = null; + _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS, false); + } + } + } +} \ No newline at end of file diff --git a/Assets/LIV/Scripts/SDKBridge.cs b/Assets/LIV/Scripts/SDKBridge.cs new file mode 100644 index 0000000..e6e180f --- /dev/null +++ b/Assets/LIV/Scripts/SDKBridge.cs @@ -0,0 +1,366 @@ +using UnityEngine; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace LIV.SDK.Unity +{ + public static class SDKBridge + { + #if (UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN) && UNITY_64 + #region Interop + + [DllImport("LIV_Bridge")] + static extern IntPtr GetRenderEventFunc(); + + [DllImport("LIV_Bridge", EntryPoint = "LivCaptureIsActive")] + [return: MarshalAs(UnmanagedType.U1)] + static extern bool GetIsCaptureActive(); + + [DllImport("LIV_Bridge", EntryPoint = "LivCaptureWidth")] + static extern int GetTextureWidth(); + + [DllImport("LIV_Bridge", EntryPoint = "LivCaptureHeight")] + static extern int GetTextureHeight(); + + [DllImport("LIV_Bridge", EntryPoint = "LivCaptureSetTextureFromUnity")] + static extern void SetTexture(IntPtr texture); + + //// Acquire a frame from the compositor, allowing atomic access to its properties - most current one by default + [DllImport("LIV_Bridge", EntryPoint = "AcquireCompositorFrame")] + public static extern int AcquireCompositorFrame(ulong timestamp); + + [DllImport("LIV_Bridge", EntryPoint = "ReleaseCompositorFrame")] + public static extern int ReleaseCompositorFrame(); + + // Get timestamp of SDK2 object (C# timestamp) - must be an object in the bridge, not a copy. + [DllImport("LIV_Bridge", EntryPoint = "GetObjectTimeStamp")] + public static extern ulong GetObjectTimeStamp(IntPtr obj); + + // Get current time in C# ticks + [DllImport("LIV_Bridge", EntryPoint = "GetCurrentTimeTicks")] + static extern ulong GetCurrentTimeTicks(); + + // Get object tag of SDK2 object - must be an object in the bridge, not a copy. + [DllImport("LIV_Bridge", EntryPoint = "GetObjectTag")] + public static extern ulong GetObjectTag(IntPtr obj); + + // Get a frame object from the compositor + [DllImport("LIV_Bridge", EntryPoint = "GetCompositorFrameObject")] + public static extern IntPtr GetCompositorFrameObject(ulong tag); + + // Get a frame object from the compositor + [DllImport("LIV_Bridge", EntryPoint = "GetViewportTexture")] + public static extern IntPtr GetViewportTexture(); + + // Get a channel object from the compositor + [DllImport("LIV_Bridge", EntryPoint = "GetCompositorChannelObject")] + public static extern IntPtr GetCompositorChannelObject(int slot, ulong tag, ulong timestamp); + + // Get a channel object from our own source channel + [DllImport("LIV_Bridge", EntryPoint = "GetChannelObject")] + public static extern IntPtr GetChannelObject(int slot, ulong tag, ulong timestamp); + + // Write an object to our channel + [DllImport("LIV_Bridge", EntryPoint = "AddObjectToChannel")] + public static extern int AddObjectToChannel(int slot, IntPtr obj, int objectsize, ulong tag); + + // Write an object to the compostor's channel + [DllImport("LIV_Bridge", EntryPoint = "AddObjectToCompositorChannel")] + public static extern int AddObjectToCompositorChannel(int slot, IntPtr obj, int objectsize, ulong tag); + + // Add a structure/object to the current frame / Considering if its simpler to combine with AddObjectToChannel with 0 being the frame + [DllImport("LIV_Bridge", EntryPoint = "AddObjectToFrame")] + public static extern int AddObjectToFrame(IntPtr obj, int objectsize, ulong tag); + + // Helper to add strings + [DllImport("LIV_Bridge", EntryPoint = "AddObjectToFrame")] + public static extern int AddStringToFrame(IntPtr str, ulong tag); + + [DllImport("LIV_Bridge", EntryPoint = "AddStringToChannel")] + public static extern int AddStringToChannel(int slot, IntPtr str, int length, ulong tag); + + // Create a new frame for rendering / native code does this already - so probably don't use + [DllImport("LIV_Bridge", EntryPoint = "NewFrame")] + public static extern int NewFrame(); + + // Commit the frame early - not recommended - best to let the next NewFrame commit the frame to avoid pipeline stalls + [DllImport("LIV_Bridge", EntryPoint = "CommitFrame")] + public static extern IntPtr CommitFrame(); + + // Add a copy of a unity texture to the bridge + [DllImport("LIV_Bridge", EntryPoint = "addsharedtexture")] + public static extern int addsharedtexture(int width, int height, int format, IntPtr sourcetexture, ulong tag); + + [DllImport("LIV_Bridge", EntryPoint = "addtexture")] + public static extern int addtexture(IntPtr sourcetexture, ulong tag); + + [DllImport("LIV_Bridge", EntryPoint = "PublishTextures")] + public static extern void PublishTextures(); + + [DllImport("LIV_Bridge", EntryPoint = "updateinputframe")] + public static extern IntPtr updatinputframe(IntPtr InputFrame); + + [DllImport("LIV_Bridge", EntryPoint = "setinputframe")] + public static extern IntPtr setinputframe(float x, float y, float z, float q0, float q1, float q2, float q3, float fov, int priority); + + [DllImport("LIV_Bridge", EntryPoint = "setfeature")] + public static extern ulong setfeature(ulong feature); + + [DllImport("LIV_Bridge", EntryPoint = "clearfeature")] + public static extern ulong clearfeature(ulong feature); + #endregion + #else + public static int AddStringToChannel(int slot, IntPtr str, int length, ulong tag) { return -2; } + public static int addtexture(IntPtr sourcetexture, ulong tag) { return -2; } + public static ulong GetObjectTimeStamp(IntPtr obj) { return 0; } + public static ulong GetCurrentTimeTicks() { return 0; } + static bool GetIsCaptureActive() { return false; } + public static IntPtr GetRenderEventFunc() { return IntPtr.Zero; } + public static IntPtr GetCompositorChannelObject(int slot, ulong tag, ulong timestamp) { return IntPtr.Zero; } + public static int AddObjectToCompositorChannel(int slot, IntPtr obj, int objectsize, ulong tag) { return -2; } + public static int AddObjectToFrame(IntPtr obj, int objectsize, ulong tag) { return -2; } + public static IntPtr updatinputframe(IntPtr InputFrame) { return IntPtr.Zero; } + public static IntPtr GetViewportTexture() { return IntPtr.Zero; } + public static IntPtr GetChannelObject(int slot, ulong tag, ulong timestamp) { return IntPtr.Zero; } + public static int AddObjectToChannel(int slot, IntPtr obj, int objectsize, ulong tag) { return -2; } + #endif + + public struct SDKInjection + { + public bool active; + public System.Action action; + public T data; + } + + static SDKInjection _injection_SDKInputFrame = new SDKInjection() + { + active = false, + action = null, + data = SDKInputFrame.empty + }; + + static SDKInjection _injection_SDKResolution = new SDKInjection() + { + active = false, + action = null, + data = SDKResolution.zero + }; + + static SDKInjection _injection_IsActive = new SDKInjection() + { + active = false, + action = null, + data = false + }; + + static bool _injection_DisableSubmit = false; + static bool _injection_DisableSubmitApplicationOutput = false; + static bool _injection_DisableAddTexture = false; + static bool _injection_DisableCreateFrame = false; + + // Get the tag code for a string - won't win any awards - pre-compute these and use constants. + public static ulong Tag(string str) + { + ulong tag = 0; + for (int i = 0; i < str.Length; i++) + { + if (i == 8) break; + char c = str[i]; + tag |= (((ulong)(c & 255)) << (i * 8)); + } + + return tag; + } + + public static void AddString(string tag, string value, int slot) + { + var utf8 = Encoding.UTF8; + byte[] utfBytes = utf8.GetBytes(value); + GCHandle gch = GCHandle.Alloc(utfBytes, GCHandleType.Pinned); + AddStringToChannel(slot, Marshal.UnsafeAddrOfPinnedArrayElement(utfBytes, 0), utfBytes.Length, Tag(tag)); + gch.Free(); + } + + public static void AddTexture(SDKTexture texture, ulong tag) + { + GCHandle gch = GCHandle.Alloc(texture, GCHandleType.Pinned); + addtexture(gch.AddrOfPinnedObject(), tag); + gch.Free(); + } + + public static ulong GetObjectTime(IntPtr objectptr) + { + return GetObjectTimeStamp(objectptr) + 621355968000000000; + } + + public static ulong GetCurrentTime() + { + return GetCurrentTimeTicks() + 621355968000000000; + } + + public static bool IsActive { + get { + if (_injection_IsActive.active) + { + return _injection_IsActive.data; + } + return GetIsCaptureActive(); + } + } + + public static void IssuePluginEvent() + { + if (_injection_DisableSubmit) return; + GL.IssuePluginEvent(GetRenderEventFunc(), 2); + } + + public static void SubmitApplicationOutput(SDKApplicationOutput applicationOutput) + { + if (_injection_DisableSubmitApplicationOutput) return; + AddString("APPNAME", applicationOutput.applicationName, 5); + AddString("APPVER", applicationOutput.applicationVersion, 5); + AddString("ENGNAME", applicationOutput.engineName, 5); + AddString("ENGVER", applicationOutput.engineVersion, 5); + AddString("GFXAPI", applicationOutput.graphicsAPI, 5); + AddString("SDKID", applicationOutput.sdkID, 5); + AddString("SDKVER", applicationOutput.sdkVersion, 5); + AddString("SUPPORT", applicationOutput.supportedFeatures.ToString(), 5); + AddString("XRNAME", applicationOutput.xrDeviceName, 5); + } + + public static bool GetStructFromGlobalChannel ( ref T mystruct, int channel, ulong tag) + { + IntPtr structPtr = GetCompositorChannelObject(channel, tag, UInt64.MaxValue); + if (structPtr == IntPtr.Zero) return false; + mystruct= (T)Marshal.PtrToStructure(structPtr, typeof(T)); + return true; + } + + public static int AddStructToGlobalChannel(ref T mystruct, int channel, ulong tag) + { + GCHandle gch = GCHandle.Alloc(mystruct, GCHandleType.Pinned); + int output = AddObjectToCompositorChannel(channel, gch.AddrOfPinnedObject(), Marshal.SizeOf(mystruct), tag); + gch.Free(); + return output; + } + + public static bool GetStructFromLocalChannel(ref T mystruct, int channel, ulong tag) + { + IntPtr structPtr = GetChannelObject(channel, tag, UInt64.MaxValue); + if (structPtr == IntPtr.Zero) return false; + mystruct = (T)Marshal.PtrToStructure(structPtr, typeof(T)); + return true; + } + + public static int AddStructToLocalChannel(ref T mystruct, int channel, ulong tag) + { + GCHandle gch = GCHandle.Alloc(mystruct, GCHandleType.Pinned); + int output = AddObjectToChannel(channel, gch.AddrOfPinnedObject(), Marshal.SizeOf(mystruct), tag); + gch.Free(); + return output; + } + + // Add ANY structure to the current frame + public static void AddStructToFrame(ref T mystruct, ulong tag) + { + GCHandle gch = GCHandle.Alloc(mystruct, GCHandleType.Pinned); + AddObjectToFrame(gch.AddrOfPinnedObject(), Marshal.SizeOf(mystruct), tag); + gch.Free(); + } + + + /// + /// Update the master pose sent to ALL applications. + /// + /// when called initialy, having the flags set to 0 will return the current pose (which includes resolution - which you might need) + /// If you wish to change the pose, change the parts of the structures you need to, and set the appropriate flag to update. + /// atm, the flags will be for Pose, Stage, Clipping Plane, and resolution. + /// + /// + /// + /// The current pose - could be yours, someone elses, or a combination + + public static bool UpdateInputFrame(ref SDKInputFrame setframe) + { + if (_injection_SDKInputFrame.active && _injection_SDKInputFrame.action != null) + { + _injection_SDKInputFrame.action.Invoke(); + setframe = _injection_SDKInputFrame.data; + } + else + { + // Pin the object briefly so we can send it to the API without it being accidentally garbage collected + GCHandle gch = GCHandle.Alloc(setframe, GCHandleType.Pinned); + IntPtr structPtr = updatinputframe(gch.AddrOfPinnedObject()); + gch.Free(); + + if (structPtr == IntPtr.Zero) + { + setframe = SDKInputFrame.empty; + return false; + } + + setframe = (SDKInputFrame)Marshal.PtrToStructure(structPtr, typeof(SDKInputFrame)); + _injection_SDKInputFrame.data = setframe; + } + + return true; + } + + public static SDKTexture GetViewfinderTexture() + { + SDKTexture overlaytexture = SDKTexture.empty; + IntPtr structPtr = GetCompositorChannelObject(11, Tag("OUTTEX"), UInt64.MaxValue); + if (structPtr == IntPtr.Zero) return new SDKTexture(); + overlaytexture = (SDKTexture)Marshal.PtrToStructure(structPtr, typeof(SDKTexture)); + return overlaytexture; + } + + public static void AddTexture(SDKTexture texture) + { + if (_injection_DisableAddTexture) return; + string tag = ""; + switch (texture.id) + { + case TEXTURE_ID.BACKGROUND_COLOR_BUFFER_ID: + tag = "BGCTEX"; + break; + case TEXTURE_ID.FOREGROUND_COLOR_BUFFER_ID: + tag = "FGCTEX"; + break; + case TEXTURE_ID.OPTIMIZED_COLOR_BUFFER_ID: + tag = "OPTTEX"; + break; + } + AddTexture(texture, Tag(tag)); + } + + public static void CreateFrame(SDKOutputFrame frame) + { + if (_injection_DisableCreateFrame) return; + GCHandle gch = GCHandle.Alloc(frame, GCHandleType.Pinned); + AddObjectToFrame(gch.AddrOfPinnedObject(), Marshal.SizeOf(frame), Tag("OUTFRAME")); + gch.Free(); + } + + public static void SetGroundPlane(SDKPlane groundPlane) + { + AddStructToGlobalChannel(ref groundPlane, 2, SDKBridge.Tag("SetGND")); + } + + public static bool GetResolution(ref SDKResolution sdkResolution) + { + if(_injection_SDKResolution.active && _injection_SDKResolution.action != null) + { + _injection_SDKResolution.action.Invoke(); + sdkResolution = _injection_SDKResolution.data; + return true; + } + + bool output = GetStructFromLocalChannel(ref sdkResolution, 15, SDKBridge.Tag("SDKRes")); + _injection_SDKResolution.data = sdkResolution; + return output; + } + } +} diff --git a/Assets/LIV/Scripts/SDKRender.cs b/Assets/LIV/Scripts/SDKRender.cs new file mode 100644 index 0000000..1fa2e3f --- /dev/null +++ b/Assets/LIV/Scripts/SDKRender.cs @@ -0,0 +1,456 @@ +#if !LIV_UNIVERSAL_RENDER +using UnityEngine; +using UnityEngine.Rendering; + +namespace LIV.SDK.Unity +{ + public partial class SDKRender : System.IDisposable + { + // Renders the clip plane in the foreground texture + private CommandBuffer _clipPlaneCommandBuffer = null; + // Renders the clipped opaque content in to the foreground texture alpha + private CommandBuffer _combineAlphaCommandBuffer = null; + // Captures texture before post-effects + private CommandBuffer _captureTextureCommandBuffer = null; + // Renders captured texture + private CommandBuffer _applyTextureCommandBuffer = null; + // Renders background and foreground in single render + private CommandBuffer _optimizedRenderingCommandBuffer = null; + + private CameraEvent _clipPlaneCameraEvent = CameraEvent.AfterForwardOpaque; + private CameraEvent _clipPlaneCombineAlphaCameraEvent = CameraEvent.AfterEverything; + private CameraEvent _captureTextureEvent = CameraEvent.BeforeImageEffects; + private CameraEvent _applyTextureEvent = CameraEvent.AfterEverything; + private CameraEvent _optimizedRenderingCameraEvent = CameraEvent.AfterEverything; + + // Tessellated quad + private Mesh _clipPlaneMesh = null; + // Clear material + private Material _clipPlaneSimpleMaterial = null; + // Transparent material for visual debugging + private Material _clipPlaneSimpleDebugMaterial = null; + // Tessellated height map clear material + private Material _clipPlaneComplexMaterial = null; + // Tessellated height map clear material for visual debugging + private Material _clipPlaneComplexDebugMaterial = null; + private Material _writeOpaqueToAlphaMaterial = null; + private Material _combineAlphaMaterial = null; + private Material _writeMaterial = null; + private Material _forceForwardRenderingMaterial = null; + + private RenderTexture _backgroundRenderTexture = null; + private RenderTexture _foregroundRenderTexture = null; + private RenderTexture _optimizedRenderTexture = null; + private RenderTexture _complexClipPlaneRenderTexture = null; + + Material GetClipPlaneMaterial(bool debugClipPlane, bool complexClipPlane, ColorWriteMask colorWriteMask) + { + Material output; + + if (complexClipPlane) + { + output = debugClipPlane ? _clipPlaneComplexDebugMaterial : _clipPlaneComplexMaterial; + output.SetTexture(SDKShaders.LIV_CLIP_PLANE_HEIGHT_MAP_PROPERTY, _complexClipPlaneRenderTexture); + output.SetFloat(SDKShaders.LIV_TESSELLATION_PROPERTY, _inputFrame.clipPlane.tesselation); + } + else + { + output = debugClipPlane ? _clipPlaneSimpleDebugMaterial : _clipPlaneSimpleMaterial; + } + + output.SetInt(SDKShaders.LIV_COLOR_MASK, (int)colorWriteMask); + return output; + } + + Material GetGroundClipPlaneMaterial(bool debugClipPlane, ColorWriteMask colorWriteMask) + { + Material output; + output = debugClipPlane ? _clipPlaneSimpleDebugMaterial : _clipPlaneSimpleMaterial; + output.SetInt(SDKShaders.LIV_COLOR_MASK, (int)colorWriteMask); + return output; + } + + bool useDeferredRendering { + get { + return _cameraInstance.actualRenderingPath == RenderingPath.DeferredLighting || + _cameraInstance.actualRenderingPath == RenderingPath.DeferredShading; + } + } + + bool interlacedRendering { + get { + return SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.INTERLACED_RENDER); + } + } + + bool canRenderBackground { + get { + if (interlacedRendering) + { + // Render only if frame is even + if (Time.frameCount % 2 != 0) return false; + } + return SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.BACKGROUND_RENDER) && _backgroundRenderTexture != null; + } + } + + bool canRenderForeground { + get { + if (interlacedRendering) + { + // Render only if frame is odd + if (Time.frameCount % 2 != 1) return false; + } + return SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.FOREGROUND_RENDER) && _foregroundRenderTexture != null; + } + } + + bool canRenderOptimized { + get { + return SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.OPTIMIZED_RENDER) && _optimizedRenderTexture != null; ; + } + } + + public SDKRender(LIV liv) + { + _liv = liv; + CreateAssets(); + } + + public void Render() + { + UpdateBridgeResolution(); + UpdateBridgeInputFrame(); + SDKUtils.ApplyUserSpaceTransform(this); + UpdateTextures(); + InvokePreRender(); + if (canRenderBackground) RenderBackground(); + if (canRenderForeground) RenderForeground(); + if (canRenderOptimized) RenderOptimized(); + IvokePostRender(); + SDKUtils.CreateBridgeOutputFrame(this); + SDKBridge.IssuePluginEvent(); + } + + // Default render without any special changes + private void RenderBackground() + { + SDKUtils.SetCamera(_cameraInstance, _cameraInstance.transform, _inputFrame, localToWorldMatrix, spectatorLayerMask); + _cameraInstance.targetTexture = _backgroundRenderTexture; + + RenderTexture tempRenderTexture = null; + + bool overridePostProcessing = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.OVERRIDE_POST_PROCESSING); + if (overridePostProcessing) + { + tempRenderTexture = RenderTexture.GetTemporary(_backgroundRenderTexture.width, _backgroundRenderTexture.height, 0, _backgroundRenderTexture.format); +#if UNITY_EDITOR + tempRenderTexture.name = "LIV.TemporaryRenderTexture"; +#endif + _captureTextureCommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, tempRenderTexture); + _applyTextureCommandBuffer.Blit(tempRenderTexture, BuiltinRenderTextureType.CurrentActive); + + _cameraInstance.AddCommandBuffer(_captureTextureEvent, _captureTextureCommandBuffer); + _cameraInstance.AddCommandBuffer(_applyTextureEvent, _applyTextureCommandBuffer); + } + + SDKShaders.StartRendering(); + SDKShaders.StartBackgroundRendering(); + InvokePreRenderBackground(); + SendTextureToBridge(_backgroundRenderTexture, TEXTURE_ID.BACKGROUND_COLOR_BUFFER_ID); + _cameraInstance.Render(); + InvokePostRenderBackground(); + _cameraInstance.targetTexture = null; + SDKShaders.StopBackgroundRendering(); + SDKShaders.StopRendering(); + + if (overridePostProcessing) + { + _cameraInstance.RemoveCommandBuffer(_captureTextureEvent, _captureTextureCommandBuffer); + _cameraInstance.RemoveCommandBuffer(_applyTextureEvent, _applyTextureCommandBuffer); + + _captureTextureCommandBuffer.Clear(); + _applyTextureCommandBuffer.Clear(); + + RenderTexture.ReleaseTemporary(tempRenderTexture); + } + } + + // Extract the image which is in front of our clip plane + // The compositing is heavily relying on the alpha channel, therefore we want to make sure it does + // not get corrupted by the postprocessing or any shader + private void RenderForeground() + { + bool debugClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.DEBUG_CLIP_PLANE); + bool renderComplexClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.COMPLEX_CLIP_PLANE); + bool renderGroundClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.GROUND_CLIP_PLANE); + bool overridePostProcessing = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.OVERRIDE_POST_PROCESSING); + bool fixPostEffectsAlpha = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.FIX_FOREGROUND_ALPHA) | _liv.fixPostEffectsAlpha; + + MonoBehaviour[] behaviours = null; + bool[] wasBehaviourEnabled = null; + if (disableStandardAssets) SDKUtils.DisableStandardAssets(_cameraInstance, ref behaviours, ref wasBehaviourEnabled); + + // Capture camera defaults + CameraClearFlags capturedClearFlags = _cameraInstance.clearFlags; + Color capturedBgColor = _cameraInstance.backgroundColor; + Color capturedFogColor = RenderSettings.fogColor; + + // Make sure that fog does not corrupt alpha channel + RenderSettings.fogColor = new Color(capturedFogColor.r, capturedFogColor.g, capturedFogColor.b, 0f); + SDKUtils.SetCamera(_cameraInstance, _cameraInstance.transform, _inputFrame, localToWorldMatrix, spectatorLayerMask); + _cameraInstance.clearFlags = CameraClearFlags.Color; + _cameraInstance.backgroundColor = Color.clear; + _cameraInstance.targetTexture = _foregroundRenderTexture; + + RenderTexture capturedAlphaRenderTexture = RenderTexture.GetTemporary(_foregroundRenderTexture.width, _foregroundRenderTexture.height, 0, _foregroundRenderTexture.format); +#if UNITY_EDITOR + capturedAlphaRenderTexture.name = "LIV.CapturedAlphaRenderTexture"; +#endif + + // Render opaque pixels into alpha channel + _clipPlaneCommandBuffer.DrawMesh(_clipPlaneMesh, Matrix4x4.identity, _writeOpaqueToAlphaMaterial, 0, 0); + + // Render clip plane + Matrix4x4 clipPlaneTransform = localToWorldMatrix * (Matrix4x4)_inputFrame.clipPlane.transform; + _clipPlaneCommandBuffer.DrawMesh(_clipPlaneMesh, clipPlaneTransform, + GetClipPlaneMaterial(debugClipPlane, renderComplexClipPlane, ColorWriteMask.All), 0, 0); + + // Render ground clip plane + if (renderGroundClipPlane) + { + Matrix4x4 groundClipPlaneTransform = localToWorldMatrix * (Matrix4x4)_inputFrame.groundClipPlane.transform; + _clipPlaneCommandBuffer.DrawMesh(_clipPlaneMesh, groundClipPlaneTransform, + GetGroundClipPlaneMaterial(debugClipPlane, ColorWriteMask.All), 0, 0); + } + + // Copy alpha in to texture + _clipPlaneCommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, capturedAlphaRenderTexture); + _cameraInstance.AddCommandBuffer(_clipPlaneCameraEvent, _clipPlaneCommandBuffer); + + // Fix alpha corruption by post processing + RenderTexture tempRenderTexture = null; + if (overridePostProcessing || fixPostEffectsAlpha) + { + tempRenderTexture = RenderTexture.GetTemporary(_foregroundRenderTexture.width, _foregroundRenderTexture.height, 0, _foregroundRenderTexture.format); +#if UNITY_EDITOR + tempRenderTexture.name = "LIV.TemporaryRenderTexture"; +#endif + _captureTextureCommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, tempRenderTexture); + _cameraInstance.AddCommandBuffer(_captureTextureEvent, _captureTextureCommandBuffer); + + _writeMaterial.SetInt(SDKShaders.LIV_COLOR_MASK, overridePostProcessing ? (int)ColorWriteMask.All : (int)ColorWriteMask.Alpha); + _applyTextureCommandBuffer.Blit(tempRenderTexture, BuiltinRenderTextureType.CurrentActive, _writeMaterial); + _cameraInstance.AddCommandBuffer(_applyTextureEvent, _applyTextureCommandBuffer); + } + + // Combine captured alpha with result alpha + _combineAlphaMaterial.SetInt(SDKShaders.LIV_COLOR_MASK, (int)ColorWriteMask.Alpha); + _combineAlphaCommandBuffer.Blit(capturedAlphaRenderTexture, BuiltinRenderTextureType.CurrentActive, _combineAlphaMaterial); + _cameraInstance.AddCommandBuffer(_clipPlaneCombineAlphaCameraEvent, _combineAlphaCommandBuffer); + + if (useDeferredRendering) SDKUtils.ForceForwardRendering(cameraInstance, _clipPlaneMesh, _forceForwardRenderingMaterial); + + SDKShaders.StartRendering(); + SDKShaders.StartForegroundRendering(); + InvokePreRenderForeground(); + SendTextureToBridge(_foregroundRenderTexture, TEXTURE_ID.FOREGROUND_COLOR_BUFFER_ID); + _cameraInstance.Render(); + InvokePostRenderForeground(); + _cameraInstance.targetTexture = null; + SDKShaders.StopForegroundRendering(); + SDKShaders.StopRendering(); + + if (overridePostProcessing || fixPostEffectsAlpha) + { + _cameraInstance.RemoveCommandBuffer(_captureTextureEvent, _captureTextureCommandBuffer); + _cameraInstance.RemoveCommandBuffer(_applyTextureEvent, _applyTextureCommandBuffer); + + _captureTextureCommandBuffer.Clear(); + _applyTextureCommandBuffer.Clear(); + + RenderTexture.ReleaseTemporary(tempRenderTexture); + } + + _cameraInstance.RemoveCommandBuffer(_clipPlaneCameraEvent, _clipPlaneCommandBuffer); + _cameraInstance.RemoveCommandBuffer(_clipPlaneCombineAlphaCameraEvent, _combineAlphaCommandBuffer); + + RenderTexture.ReleaseTemporary(capturedAlphaRenderTexture); + + _clipPlaneCommandBuffer.Clear(); + _combineAlphaCommandBuffer.Clear(); + + // Revert camera defaults + _cameraInstance.clearFlags = capturedClearFlags; + _cameraInstance.backgroundColor = capturedBgColor; + RenderSettings.fogColor = capturedFogColor; + + SDKUtils.RestoreStandardAssets(ref behaviours, ref wasBehaviourEnabled); + } + + // Renders a single camera in a single texture with occlusion only from opaque objects. + // This is the most performant option for mixed reality. + // It does not support any transparency in the foreground layer. + private void RenderOptimized() + { + bool debugClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.DEBUG_CLIP_PLANE); + bool renderComplexClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.COMPLEX_CLIP_PLANE); + bool renderGroundClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.GROUND_CLIP_PLANE); + + SDKUtils.SetCamera(_cameraInstance, _cameraInstance.transform, _inputFrame, localToWorldMatrix, spectatorLayerMask); + _cameraInstance.targetTexture = _optimizedRenderTexture; + + // Clear alpha channel + _writeMaterial.SetInt(SDKShaders.LIV_COLOR_MASK, (int)ColorWriteMask.Alpha); + _optimizedRenderingCommandBuffer.Blit(BuiltinRenderTextureType.None, BuiltinRenderTextureType.CurrentActive, _writeMaterial); + + // Render opaque pixels into alpha channel + _writeOpaqueToAlphaMaterial.SetInt(SDKShaders.LIV_COLOR_MASK, (int)ColorWriteMask.Alpha); + _optimizedRenderingCommandBuffer.DrawMesh(_clipPlaneMesh, Matrix4x4.identity, _writeOpaqueToAlphaMaterial, 0, 0); + + // Render clip plane + Matrix4x4 clipPlaneTransform = localToWorldMatrix * (Matrix4x4)_inputFrame.clipPlane.transform; + _optimizedRenderingCommandBuffer.DrawMesh(_clipPlaneMesh, clipPlaneTransform, + GetClipPlaneMaterial(debugClipPlane, renderComplexClipPlane, ColorWriteMask.Alpha), 0, 0); + + // Render ground clip plane + if (renderGroundClipPlane) + { + Matrix4x4 groundClipPlaneTransform = localToWorldMatrix * (Matrix4x4)_inputFrame.groundClipPlane.transform; + _optimizedRenderingCommandBuffer.DrawMesh(_clipPlaneMesh, groundClipPlaneTransform, + GetGroundClipPlaneMaterial(debugClipPlane, ColorWriteMask.Alpha), 0, 0); + } + + _cameraInstance.AddCommandBuffer(CameraEvent.AfterEverything, _optimizedRenderingCommandBuffer); + + // TODO: this is just proprietary + SDKShaders.StartRendering(); + SDKShaders.StartBackgroundRendering(); + InvokePreRenderBackground(); + SendTextureToBridge(_optimizedRenderTexture, TEXTURE_ID.OPTIMIZED_COLOR_BUFFER_ID); + _cameraInstance.Render(); + InvokePostRenderBackground(); + _cameraInstance.targetTexture = null; + SDKShaders.StopBackgroundRendering(); + SDKShaders.StopRendering(); + + _cameraInstance.RemoveCommandBuffer(CameraEvent.AfterEverything, _optimizedRenderingCommandBuffer); + _optimizedRenderingCommandBuffer.Clear(); + } + + private void CreateAssets() + { + bool cameraReferenceEnabled = cameraReference.enabled; + if (cameraReferenceEnabled) + { + cameraReference.enabled = false; + } + bool cameraReferenceActive = cameraReference.gameObject.activeSelf; + if (cameraReferenceActive) + { + cameraReference.gameObject.SetActive(false); + } + + GameObject cloneGO = (GameObject)Object.Instantiate(cameraReference.gameObject, _liv.stage); + _cameraInstance = (Camera)cloneGO.GetComponent("Camera"); + + SDKUtils.CleanCameraBehaviours(_cameraInstance, _liv.excludeBehaviours); + + if (cameraReferenceActive != cameraReference.gameObject.activeSelf) + { + cameraReference.gameObject.SetActive(cameraReferenceActive); + } + if (cameraReferenceEnabled != cameraReference.enabled) + { + cameraReference.enabled = cameraReferenceEnabled; + } + + _cameraInstance.name = "LIV Camera"; + if (_cameraInstance.tag == "MainCamera") + { + _cameraInstance.tag = "Untagged"; + } + + _cameraInstance.transform.localScale = Vector3.one; + _cameraInstance.rect = new Rect(0, 0, 1, 1); + _cameraInstance.depth = 0; +#if UNITY_5_4_OR_NEWER + _cameraInstance.stereoTargetEye = StereoTargetEyeMask.None; +#endif +#if UNITY_5_6_OR_NEWER + _cameraInstance.allowMSAA = false; +#endif + _cameraInstance.enabled = false; + _cameraInstance.gameObject.SetActive(true); + + _clipPlaneMesh = new Mesh(); + SDKUtils.CreateClipPlane(_clipPlaneMesh, 10, 10, true, 1000f); + _clipPlaneSimpleMaterial = new Material(Shader.Find(SDKShaders.LIV_CLIP_PLANE_SIMPLE_SHADER)); + _clipPlaneSimpleDebugMaterial = new Material(Shader.Find(SDKShaders.LIV_CLIP_PLANE_SIMPLE_DEBUG_SHADER)); + _clipPlaneComplexMaterial = new Material(Shader.Find(SDKShaders.LIV_CLIP_PLANE_COMPLEX_SHADER)); + _clipPlaneComplexDebugMaterial = new Material(Shader.Find(SDKShaders.LIV_CLIP_PLANE_COMPLEX_DEBUG_SHADER)); + _writeOpaqueToAlphaMaterial = new Material(Shader.Find(SDKShaders.LIV_WRITE_OPAQUE_TO_ALPHA_SHADER)); + _combineAlphaMaterial = new Material(Shader.Find(SDKShaders.LIV_COMBINE_ALPHA_SHADER)); + _writeMaterial = new Material(Shader.Find(SDKShaders.LIV_WRITE_SHADER)); + _forceForwardRenderingMaterial = new Material(Shader.Find(SDKShaders.LIV_FORCE_FORWARD_RENDERING_SHADER)); + _clipPlaneCommandBuffer = new CommandBuffer(); + _combineAlphaCommandBuffer = new CommandBuffer(); + _captureTextureCommandBuffer = new CommandBuffer(); + _applyTextureCommandBuffer = new CommandBuffer(); + _optimizedRenderingCommandBuffer = new CommandBuffer(); + +#if UNITY_EDITOR + _clipPlaneMesh.name = "LIV.clipPlane"; + _clipPlaneSimpleMaterial.name = "LIV.clipPlaneSimple"; + _clipPlaneSimpleDebugMaterial.name = "LIV.clipPlaneSimpleDebug"; + _clipPlaneComplexMaterial.name = "LIV.clipPlaneComplex"; + _clipPlaneComplexDebugMaterial.name = "LIV.clipPlaneComplexDebug"; + _writeOpaqueToAlphaMaterial.name = "LIV.writeOpaqueToAlpha"; + _combineAlphaMaterial.name = "LIV.combineAlpha"; + _writeMaterial.name = "LIV.write"; + _forceForwardRenderingMaterial.name = "LIV.forceForwardRendering"; + _clipPlaneCommandBuffer.name = "LIV.renderClipPlanes"; + _combineAlphaCommandBuffer.name = "LIV.foregroundCombineAlpha"; + _captureTextureCommandBuffer.name = "LIV.captureTexture"; + _applyTextureCommandBuffer.name = "LIV.applyTexture"; + _optimizedRenderingCommandBuffer.name = "LIV.optimizedRendering"; +#endif + } + + private void DestroyAssets() + { + if (_cameraInstance != null) + { + Object.Destroy(_cameraInstance.gameObject); + _cameraInstance = null; + } + + SDKUtils.DestroyObject(ref _clipPlaneMesh); + SDKUtils.DestroyObject(ref _clipPlaneSimpleMaterial); + SDKUtils.DestroyObject(ref _clipPlaneSimpleDebugMaterial); + SDKUtils.DestroyObject(ref _clipPlaneComplexMaterial); + SDKUtils.DestroyObject(ref _clipPlaneComplexDebugMaterial); + SDKUtils.DestroyObject(ref _writeOpaqueToAlphaMaterial); + SDKUtils.DestroyObject(ref _combineAlphaMaterial); + SDKUtils.DestroyObject(ref _writeMaterial); + SDKUtils.DestroyObject(ref _forceForwardRenderingMaterial); + + SDKUtils.DisposeObject(ref _clipPlaneCommandBuffer); + SDKUtils.DisposeObject(ref _combineAlphaCommandBuffer); + SDKUtils.DisposeObject(ref _captureTextureCommandBuffer); + SDKUtils.DisposeObject(ref _applyTextureCommandBuffer); + SDKUtils.DisposeObject(ref _optimizedRenderingCommandBuffer); + } + + public void Dispose() + { + ReleaseBridgePoseControl(); + DestroyAssets(); + SDKUtils.DestroyTexture(ref _backgroundRenderTexture); + SDKUtils.DestroyTexture(ref _foregroundRenderTexture); + SDKUtils.DestroyTexture(ref _optimizedRenderTexture); + SDKUtils.DestroyTexture(ref _complexClipPlaneRenderTexture); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/LIV/Scripts/SDKRenderShared.cs b/Assets/LIV/Scripts/SDKRenderShared.cs new file mode 100644 index 0000000..027e5af --- /dev/null +++ b/Assets/LIV/Scripts/SDKRenderShared.cs @@ -0,0 +1,455 @@ +using UnityEngine; +using UnityEngine.Rendering; +using System.Collections; + +namespace LIV.SDK.Unity +{ + public partial class SDKRender : System.IDisposable + { + private LIV _liv = null; + public LIV liv { + get { + return _liv; + } + } + + private SDKOutputFrame _outputFrame = SDKOutputFrame.empty; + public SDKOutputFrame outputFrame { + get { + return _outputFrame; + } + } + + private SDKInputFrame _inputFrame = SDKInputFrame.empty; + public SDKInputFrame inputFrame { + get { + return _inputFrame; + } + } + + private SDKResolution _resolution = SDKResolution.zero; + public SDKResolution resolution { + get { + return _resolution; + } + } + + private Camera _cameraInstance = null; + public Camera cameraInstance { + get { + return _cameraInstance; + } + } + + public Camera cameraReference { + get { + return _liv.MRCameraPrefab == null ? _liv.HMDCamera : _liv.MRCameraPrefab; + } + } + + public Camera hmdCamera { + get { + return _liv.HMDCamera; + } + } + + public Transform stage { + get { + return _liv.stage; + } + } + + public Transform stageTransform { + get { + return _liv.stageTransform; + } + } + + public Matrix4x4 stageLocalToWorldMatrix { + get { + return _liv.stage == null ? Matrix4x4.identity : _liv.stage.localToWorldMatrix; + } + } + + public Matrix4x4 localToWorldMatrix { + get { + return _liv.stageTransform == null ? stageLocalToWorldMatrix : _liv.stageTransform.localToWorldMatrix; + } + } + + public int spectatorLayerMask { + get { + return _liv.spectatorLayerMask; + } + } + + public bool disableStandardAssets { + get { + return _liv.disableStandardAssets; + } + } + + private SDKPose _requestedPose = SDKPose.empty; + private int _requestedPoseFrameIndex = 0; + + /// + /// Detect if the game can actually change the pose during this frame. + /// + /// + /// Because other applications can take over the pose, the game has to know if it can take over the pose or not. + /// + /// + /// + /// public class CanControlCameraPose : MonoBehaviour + /// { + /// [SerializeField] LIV.SDK.Unity.LIV _liv; + /// + /// private void Update() + /// { + /// if(_liv.isActive) + /// { + /// Debug.Log(_liv.render.canSetPose); + /// } + /// } + /// } + /// + /// + public bool canSetPose + { + get { + if (_inputFrame.frameid == 0) return false; + return _inputFrame.priority.pose <= (sbyte)PRIORITY.GAME; + } + } + + /// + /// Control camera pose by calling this method each frame. The pose is released when you stop calling it. + /// + /// + /// By default the pose is set in worldspace, turn on local space for using the stage relative space instead. + /// + /// + /// + /// public class ControlCameraPose : MonoBehaviour + /// { + /// [SerializeField] LIV.SDK.Unity.LIV _liv; + /// [SerializeField] float _fov = 60f; + /// + /// private void Update() + /// { + /// if(_liv.isActive) + /// { + /// _liv.render.SetPose(transform.position, transform.rotation, _fov); + /// } + /// } + /// } + /// + /// + public bool SetPose(Vector3 position, Quaternion rotation, float verticalFieldOfView = 60f, bool useLocalSpace = false) + { + if (_inputFrame.frameid == 0) return false; + SDKPose inputPose = _inputFrame.pose; + float aspect = 1f; + if (_resolution.height > 0) + { + aspect = (float)_resolution.width / (float)_resolution.height; + } + + if (!useLocalSpace) + { + Matrix4x4 worldToLocal = Matrix4x4.identity; + Transform localTransform = stageTransform == null ? stage : stageTransform; + if(localTransform != null) worldToLocal = localTransform.worldToLocalMatrix; + position = worldToLocal.MultiplyPoint(position); + rotation = SDKUtils.RotateQuaternionByMatrix(worldToLocal, rotation); + } + + _requestedPose = new SDKPose() + { + localPosition = position, + localRotation = rotation, + verticalFieldOfView = verticalFieldOfView, + projectionMatrix = Matrix4x4.Perspective(verticalFieldOfView, aspect, inputPose.nearClipPlane, inputPose.farClipPlane) + }; + + _requestedPoseFrameIndex = Time.frameCount; + return _inputFrame.priority.pose <= (sbyte)PRIORITY.GAME; + } + + /// + /// Set the game ground plane. + /// + /// + /// If you wisth to use local space coordinates use local space instead. + /// The local space has to be relative to stage or stage transform if set. + /// + /// + public void SetGroundPlane(float distance, Vector3 normal, bool useLocalSpace = false) + { + float outputDistance = distance; + Vector3 outputNormal = normal; + + if (!useLocalSpace) + { + Transform localTransform = stageTransform == null ? stage : stageTransform; + Matrix4x4 worldToLocal = localTransform.worldToLocalMatrix; + Vector3 localPosition = worldToLocal.MultiplyPoint(normal * distance); + outputNormal = worldToLocal.MultiplyVector(normal); + outputDistance = -Vector3.Dot(normal, localPosition); + } + + SDKBridge.SetGroundPlane(new SDKPlane() { distance = outputDistance, normal = outputNormal }); + } + + /// + /// Set the game ground plane. + /// + /// + /// If you wisth to use local space coordinates use local space instead. + /// The local space has to be relative to stage or stage transform if set. + /// + /// + public void SetGroundPlane(Plane plane, bool useLocalSpace = false) + { + SetGroundPlane(plane.distance, plane.normal, useLocalSpace); + } + + /// + /// Set the game ground plane. + /// + /// + /// The transform up vector defines the normal of the plane and the position defines the distance. + /// By default, the transform uses world space coordinates. If you wisth to use local space coordinates + /// use local space instead. The local space has to be relative to stage or stage transform if set. + /// + /// + /// + /// + /// public class SetGround : MonoBehaviour + /// { + /// [SerializeField] LIV.SDK.Unity.LIV _liv = null; + /// + /// void Update () + /// { + /// if(_liv.isActive) + /// { + /// _liv.render.SetGroundPlane(transform); + /// } + /// } + /// } + /// + /// + public void SetGroundPlane(Transform transform, bool useLocalSpace = false) + { + if (transform == null) return; + Quaternion rotation = useLocalSpace ? transform.localRotation : transform.rotation; + Vector3 position = useLocalSpace ? transform.localPosition : transform.position; + Vector3 normal = rotation * Vector3.up; + SetGroundPlane(-Vector3.Dot(normal, position), normal, useLocalSpace); + } + + private void ReleaseBridgePoseControl() + { + _inputFrame.ReleaseControl(); + SDKBridge.UpdateInputFrame(ref _inputFrame); + } + + private void UpdateBridgeResolution() + { + SDKBridge.GetResolution(ref _resolution); + } + + private void UpdateBridgeInputFrame() + { + if (_requestedPoseFrameIndex == Time.frameCount) + { + _inputFrame.ObtainControl(); + _inputFrame.pose = _requestedPose; + _requestedPose = SDKPose.empty; + } + else + { + _inputFrame.ReleaseControl(); + } + + if (_cameraInstance != null) + { + // Near and far is always driven by game + _inputFrame.pose.nearClipPlane = _cameraInstance.nearClipPlane; + _inputFrame.pose.farClipPlane = _cameraInstance.farClipPlane; + } + + SDKBridge.UpdateInputFrame(ref _inputFrame); + } + + private void InvokePreRender() + { + if (_liv.onPreRender != null) _liv.onPreRender(this); + } + + private void IvokePostRender() + { + if (_liv.onPostRender != null) _liv.onPostRender(this); + } + + private void InvokePreRenderBackground() + { + if (_liv.onPreRenderBackground != null) _liv.onPreRenderBackground(this); + } + + private void InvokePostRenderBackground() + { + if (_liv.onPostRenderBackground != null) _liv.onPostRenderBackground(this); + } + + private void InvokePreRenderForeground() + { + if (_liv.onPreRenderForeground != null) _liv.onPreRenderForeground(this); + } + + private void InvokePostRenderForeground() + { + if (_liv.onPostRenderForeground != null) _liv.onPostRenderForeground(this); + } + + private void CreateBackgroundTexture() + { + if (SDKUtils.CreateTexture(ref _backgroundRenderTexture, _resolution.width, _resolution.height, 24, RenderTextureFormat.ARGB32)) + { +#if UNITY_EDITOR + _backgroundRenderTexture.name = "LIV.BackgroundRenderTexture"; +#endif + } + else + { + Debug.LogError("LIV: Unable to create background texture!"); + } + } + + private void CreateForegroundTexture() + { + if (SDKUtils.CreateTexture(ref _foregroundRenderTexture, _resolution.width, _resolution.height, 24, RenderTextureFormat.ARGB32)) + { +#if UNITY_EDITOR + _foregroundRenderTexture.name = "LIV.ForegroundRenderTexture"; +#endif + } + else + { + Debug.LogError("LIV: Unable to create foreground texture!"); + } + } + + private void CreateOptimizedTexture() + { + if (SDKUtils.CreateTexture(ref _optimizedRenderTexture, _resolution.width, _resolution.height, 24, RenderTextureFormat.ARGB32)) + { +#if UNITY_EDITOR + _optimizedRenderTexture.name = "LIV.OptimizedRenderTexture"; +#endif + } + else + { + Debug.LogError("LIV: Unable to create optimized texture!"); + } + } + + private void CreateComplexClipPlaneTexture() + { + if (SDKUtils.CreateTexture(ref _complexClipPlaneRenderTexture, _inputFrame.clipPlane.width, _inputFrame.clipPlane.height, 0, RenderTextureFormat.ARGB32)) + { +#if UNITY_EDITOR + _complexClipPlaneRenderTexture.name = "LIV.ComplexClipPlaneRenderTexture"; +#endif + } + else + { + Debug.LogError("LIV: Unable to create complex clip plane texture!"); + } + } + + private void UpdateTextures() + { + if (SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.BACKGROUND_RENDER)) + { + if ( + _backgroundRenderTexture == null || + _backgroundRenderTexture.width != _resolution.width || + _backgroundRenderTexture.height != _resolution.height + ) + { + CreateBackgroundTexture(); + } + } + else + { + SDKUtils.DestroyTexture(ref _backgroundRenderTexture); + } + + if (SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.FOREGROUND_RENDER)) + { + if ( + _foregroundRenderTexture == null || + _foregroundRenderTexture.width != _resolution.width || + _foregroundRenderTexture.height != _resolution.height + ) + { + CreateForegroundTexture(); + } + } + else + { + SDKUtils.DestroyTexture(ref _foregroundRenderTexture); + } + + if (SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.OPTIMIZED_RENDER)) + { + if ( + _optimizedRenderTexture == null || + _optimizedRenderTexture.width != _resolution.width || + _optimizedRenderTexture.height != _resolution.height + ) + { + CreateOptimizedTexture(); + } + } + else + { + SDKUtils.DestroyTexture(ref _optimizedRenderTexture); + } + + if (SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.COMPLEX_CLIP_PLANE)) + { + if ( + _complexClipPlaneRenderTexture == null || + _complexClipPlaneRenderTexture.width != _inputFrame.clipPlane.width || + _complexClipPlaneRenderTexture.height != _inputFrame.clipPlane.height + ) + { + CreateComplexClipPlaneTexture(); + } + } + else + { + SDKUtils.DestroyTexture(ref _complexClipPlaneRenderTexture); + } + } + + void SendTextureToBridge(RenderTexture texture, TEXTURE_ID id) + { + SDKBridge.AddTexture(new SDKTexture() + { + id = id, + texturePtr = texture.GetNativeTexturePtr(), + SharedHandle = System.IntPtr.Zero, + device = SDKUtils.GetDevice(), + dummy = 0, + type = TEXTURE_TYPE.COLOR_BUFFER, + format = TEXTURE_FORMAT.ARGB32, + colorSpace = SDKUtils.GetColorSpace(texture), + width = texture.width, + height = texture.height + }); + } + } +} \ No newline at end of file diff --git a/Assets/LIV/Scripts/SDKShaders.cs b/Assets/LIV/Scripts/SDKShaders.cs new file mode 100644 index 0000000..d82eb56 --- /dev/null +++ b/Assets/LIV/Scripts/SDKShaders.cs @@ -0,0 +1,56 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace LIV.SDK.Unity +{ + static class SDKShaders + { + public static readonly int LIV_COLOR_MASK = Shader.PropertyToID("_LivColorMask"); + public static readonly int LIV_TESSELLATION_PROPERTY = Shader.PropertyToID("_LivTessellation"); + public static readonly int LIV_CLIP_PLANE_HEIGHT_MAP_PROPERTY = Shader.PropertyToID("_LivClipPlaneHeightMap"); + + public const string LIV_MR_FOREGROUND_KEYWORD = "LIV_MR_FOREGROUND"; + public const string LIV_MR_BACKGROUND_KEYWORD = "LIV_MR_BACKGROUND"; + public const string LIV_MR_KEYWORD = "LIV_MR"; + + public const string LIV_CLIP_PLANE_SIMPLE_SHADER = "Hidden/LIV_ClipPlaneSimple"; + public const string LIV_CLIP_PLANE_SIMPLE_DEBUG_SHADER = "Hidden/LIV_ClipPlaneSimpleDebug"; + public const string LIV_CLIP_PLANE_COMPLEX_SHADER = "Hidden/LIV_ClipPlaneComplex"; + public const string LIV_CLIP_PLANE_COMPLEX_DEBUG_SHADER = "Hidden/LIV_ClipPlaneComplexDebug"; + public const string LIV_WRITE_OPAQUE_TO_ALPHA_SHADER = "Hidden/LIV_WriteOpaqueToAlpha"; + public const string LIV_COMBINE_ALPHA_SHADER = "Hidden/LIV_CombineAlpha"; + public const string LIV_WRITE_SHADER = "Hidden/LIV_Write"; + public const string LIV_FORCE_FORWARD_RENDERING_SHADER = "Hidden/LIV_ForceForwardRendering"; + + public static void StartRendering() + { + Shader.EnableKeyword(LIV_MR_KEYWORD); + } + + public static void StopRendering() + { + Shader.DisableKeyword(LIV_MR_KEYWORD); + } + + public static void StartForegroundRendering() + { + Shader.EnableKeyword(LIV_MR_FOREGROUND_KEYWORD); + } + + public static void StopForegroundRendering() + { + Shader.DisableKeyword(LIV_MR_FOREGROUND_KEYWORD); + } + + public static void StartBackgroundRendering() + { + Shader.EnableKeyword(LIV_MR_BACKGROUND_KEYWORD); + } + + public static void StopBackgroundRendering() + { + Shader.DisableKeyword(LIV_MR_BACKGROUND_KEYWORD); + } + } +} diff --git a/Assets/LIV/Scripts/SDKStructs.cs b/Assets/LIV/Scripts/SDKStructs.cs new file mode 100644 index 0000000..f539ec6 --- /dev/null +++ b/Assets/LIV/Scripts/SDKStructs.cs @@ -0,0 +1,941 @@ +using UnityEngine; +using System.Runtime.InteropServices; +using System; + +namespace LIV.SDK.Unity +{ + public struct SDKConstants + { + public const string SDK_ID = "IKZLYPTTD9OATLUM0IBASUY8UIQ9K8YZ"; + public const string SDK_VERSION = "1.5.4"; + public const string ENGINE_NAME = "unity"; + } + + public enum PRIORITY : sbyte + { + NONE = 0, + GAME = 63 + } + + [System.Flags] + public enum FEATURES : ulong + { + NONE = 0L, + BACKGROUND_RENDER = 1L, + FOREGROUND_RENDER = 1L << 1, + COMPLEX_CLIP_PLANE = 1L << 2, + BACKGROUND_DEPTH_RENDER = 1L << 3, + OVERRIDE_POST_PROCESSING = 1L << 4, + FIX_FOREGROUND_ALPHA = 1L << 5, + GROUND_CLIP_PLANE = 1L << 6, + RELEASE_CONTROL = 1L << 15, + OPTIMIZED_RENDER = 1L << 28, + INTERLACED_RENDER = 1L << 29, + DEBUG_CLIP_PLANE = 1L << 48, + } + + public enum TEXTURE_ID : uint + { + UNDEFINED = 0, + BACKGROUND_COLOR_BUFFER_ID = 10, + FOREGROUND_COLOR_BUFFER_ID = 20, + OPTIMIZED_COLOR_BUFFER_ID = 30 + } + + public enum TEXTURE_TYPE : uint + { + UNDEFINED = 0, + COLOR_BUFFER = 1 + } + + public enum TEXTURE_FORMAT : uint + { + UNDEFINED = 0, + ARGB32 = 10 + } + + public enum TEXTURE_DEVICE : uint + { + UNDEFINED = 0, + RAW = 1, + DIRECTX = 2, + OPENGL = 3, + VULKAN = 4, + METAL = 5 + } + + public enum TEXTURE_COLOR_SPACE : uint + { + UNDEFINED = 0, + LINEAR = 1, + SRGB = 2, + } + + public enum RENDERING_PIPELINE : uint + { + UNDEFINED = 0, + FORWARD = 1, + DEFERRED = 2, + VERTEX_LIT = 3, + UNIVERSAL = 4, + HIGH_DEFINITION = 5 + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKResolution + { + public int width, height; + public static SDKResolution zero { + get { + return new SDKResolution() { width = 0, height = 0 }; + } + } + + public override string ToString() + { + return +$@"SDKResolution: +width: {width} +height: {height}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKVector3 + { + public float x, y, z; + public static SDKVector3 zero { + get { + return new SDKVector3() { x = 0, y = 0, z = 0 }; + } + } + + public static SDKVector3 one { + get { + return new SDKVector3() { x = 1, y = 1, z = 1 }; + } + } + + public static SDKVector3 forward { + get { + return new SDKVector3() { x = 0, y = 0, z = 1 }; + } + } + + public static SDKVector3 up { + get { + return new SDKVector3() { x = 0, y = 1, z = 0 }; + } + } + + public static SDKVector3 right { + get { + return new SDKVector3() { x = 1, y = 0, z = 0 }; + } + } + + public static implicit operator Vector3(SDKVector3 v) + { + return new Vector3(v.x, v.y, v.z); + } + + public static implicit operator SDKVector3(Vector3 v) + { + return new SDKVector3() { x = v.x, y = v.y, z = v.z }; + } + + // Delete begin + public static SDKVector3 operator +(SDKVector3 lhs, SDKVector3 rhs) + { + SDKVector3 res; + res.x = lhs.x + rhs.x; + res.y = lhs.y + rhs.y; + res.z = lhs.z + rhs.z; + return res; + } + + public static SDKVector3 operator -(SDKVector3 lhs, SDKVector3 rhs) + { + SDKVector3 res; + res.x = lhs.x - rhs.x; + res.y = lhs.y - rhs.y; + res.z = lhs.z - rhs.z; + return res; + } + + public static SDKVector3 operator *(SDKVector3 lhs, SDKVector3 rhs) + { + SDKVector3 res; + res.x = lhs.x * rhs.x; + res.y = lhs.y * rhs.y; + res.z = lhs.z * rhs.z; + return res; + } + + public static SDKVector3 operator *(SDKVector3 lhs, float rhs) + { + SDKVector3 res; + res.x = lhs.x * rhs; + res.y = lhs.y * rhs; + res.z = lhs.z * rhs; + return res; + } + // delete end + + public override string ToString() + { + return +$@"SDKVector3: +x: {x} +y: {y} +z: {z}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKQuaternion + { + public float x, y, z, w; + public static SDKQuaternion identity { + get { + return new SDKQuaternion() { x = 0, y = 0, z = 0, w = 1.0f }; + } + } + + public static implicit operator Quaternion(SDKQuaternion v) + { + return new Quaternion(v.x, v.y, v.z, v.w); + } + + public static implicit operator SDKQuaternion(Quaternion v) + { + return new SDKQuaternion() { x = v.x, y = v.y, z = v.z, w = v.w }; + } + + // Delete begin + public static SDKQuaternion Euler(float pitch, float yaw, float roll) + { + float rollOver2 = roll * 0.5f; + float sinRollOver2 = Mathf.Sin(rollOver2); + float cosRollOver2 = Mathf.Cos(rollOver2); + float pitchOver2 = pitch * 0.5f; + float sinPitchOver2 = Mathf.Sin(pitchOver2); + float cosPitchOver2 = Mathf.Cos(pitchOver2); + float yawOver2 = yaw * 0.5f; + float sinYawOver2 = Mathf.Sin(yawOver2); + float cosYawOver2 = Mathf.Cos(yawOver2); + + var w = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2; + var x = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2; + var y = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2; + var z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2; + + return new SDKQuaternion() { x = x, y = y, z = z, w = w }; + } + + public static SDKQuaternion operator *(SDKQuaternion lhs, SDKQuaternion rhs) + { + float tx = lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y; + float ty = lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z; + float tz = lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x; + float tw = lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z; + + return new SDKQuaternion() { x = tx, y = ty, z = tz, w = tw }; + } + + public static SDKVector3 operator *(SDKQuaternion lhs, SDKVector3 rhs) + { + float tx = lhs.x * 2.0f; + float ty = lhs.y * 2.0f; + float tz = lhs.z * 2.0f; + float txx = lhs.x * tx; + float tyy = lhs.y * ty; + float tzz = lhs.z * tz; + float txy = lhs.x * ty; + float txz = lhs.x * tz; + float tyz = lhs.y * tz; + float twx = lhs.w * tx; + float twy = lhs.w * ty; + float twz = lhs.w * tz; + + SDKVector3 res; + res.x = (1.0f - (tyy + tzz)) * rhs.x + (txy - twz) * rhs.y + (txz + twy) * rhs.z; + res.y = (txy + twz) * rhs.x + (1.0f - (txx + tzz)) * rhs.y + (tyz - twx) * rhs.z; + res.z = (txz - twy) * rhs.x + (tyz + twx) * rhs.y + (1.0f - (txx + tyy)) * rhs.z; + return res; + } + // Delete end + public override string ToString() + { + return +$@"SDKQuaternion: +x: {x} +y: {y} +z: {z} +w: {w}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKMatrix4x4 + { + public float m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33; + + public static SDKMatrix4x4 identity { + get { + return new SDKMatrix4x4() + { + m00 = 1, + m01 = 0, + m02 = 0, + m03 = 0, + m10 = 0, + m11 = 1, + m12 = 0, + m13 = 0, + m20 = 0, + m21 = 0, + m22 = 1, + m23 = 0, + m30 = 0, + m31 = 0, + m32 = 0, + m33 = 1 + }; + } + } + + public static implicit operator Matrix4x4(SDKMatrix4x4 v) + { + return new Matrix4x4() + { + m00 = v.m00, + m01 = v.m01, + m02 = v.m02, + m03 = v.m03, + m10 = v.m10, + m11 = v.m11, + m12 = v.m12, + m13 = v.m13, + m20 = v.m20, + m21 = v.m21, + m22 = v.m22, + m23 = v.m23, + m30 = v.m30, + m31 = v.m31, + m32 = v.m32, + m33 = v.m33 + }; + } + + public static implicit operator SDKMatrix4x4(Matrix4x4 v) + { + return new SDKMatrix4x4() + { + m00 = v.m00, + m01 = v.m01, + m02 = v.m02, + m03 = v.m03, + m10 = v.m10, + m11 = v.m11, + m12 = v.m12, + m13 = v.m13, + m20 = v.m20, + m21 = v.m21, + m22 = v.m22, + m23 = v.m23, + m30 = v.m30, + m31 = v.m31, + m32 = v.m32, + m33 = v.m33 + }; + } + + public static SDKMatrix4x4 Perspective(float vFov, float aspect, float zNear, float zFar) + { + float vFovRad = vFov * Mathf.Deg2Rad; + float hFovRad = 2.0f * Mathf.Atan(Mathf.Tan(vFovRad * 0.5f) * aspect); + float w = 1.0f / Mathf.Tan(hFovRad * 0.5f); + float h = 1.0f / Mathf.Tan(vFovRad * 0.5f); + float q0 = (zFar + zNear) / (zNear - zFar); + float q1 = 2.0f * (zFar * zNear) / (zNear - zFar); + + return new SDKMatrix4x4() + { + m00 = w, + m01 = 0, + m02 = 0, + m03 = 0, + m10 = 0, + m11 = h, + m12 = 0, + m13 = 0, + m20 = 0, + m21 = 0, + m22 = q0, + m23 = q1, + m30 = 0, + m31 = 0, + m32 = -1, + m33 = 0 + }; + } + + // begin delete + public static SDKMatrix4x4 operator *(SDKMatrix4x4 lhs, SDKMatrix4x4 rhs) + { + SDKMatrix4x4 res = SDKMatrix4x4.identity; + + res.m00 = lhs.m00 * rhs.m00 + lhs.m01 * rhs.m10 + lhs.m02 * rhs.m20 + lhs.m03 * rhs.m30; + res.m01 = lhs.m00 * rhs.m01 + lhs.m01 * rhs.m11 + lhs.m02 * rhs.m21 + lhs.m03 * rhs.m31; + res.m02 = lhs.m00 * rhs.m02 + lhs.m01 * rhs.m12 + lhs.m02 * rhs.m22 + lhs.m03 * rhs.m32; + res.m03 = lhs.m00 * rhs.m03 + lhs.m01 * rhs.m13 + lhs.m02 * rhs.m23 + lhs.m03 * rhs.m33; + + res.m10 = lhs.m10 * rhs.m00 + lhs.m11 * rhs.m10 + lhs.m12 * rhs.m20 + lhs.m13 * rhs.m30; + res.m11 = lhs.m10 * rhs.m01 + lhs.m11 * rhs.m11 + lhs.m12 * rhs.m21 + lhs.m13 * rhs.m31; + res.m12 = lhs.m10 * rhs.m02 + lhs.m11 * rhs.m12 + lhs.m12 * rhs.m22 + lhs.m13 * rhs.m32; + res.m13 = lhs.m10 * rhs.m03 + lhs.m11 * rhs.m13 + lhs.m12 * rhs.m23 + lhs.m13 * rhs.m33; + + res.m20 = lhs.m20 * rhs.m00 + lhs.m21 * rhs.m10 + lhs.m22 * rhs.m20 + lhs.m23 * rhs.m30; + res.m21 = lhs.m20 * rhs.m01 + lhs.m21 * rhs.m11 + lhs.m22 * rhs.m21 + lhs.m23 * rhs.m31; + res.m22 = lhs.m20 * rhs.m02 + lhs.m21 * rhs.m12 + lhs.m22 * rhs.m22 + lhs.m23 * rhs.m32; + res.m23 = lhs.m20 * rhs.m03 + lhs.m21 * rhs.m13 + lhs.m22 * rhs.m23 + lhs.m23 * rhs.m33; + + res.m30 = lhs.m30 * rhs.m00 + lhs.m31 * rhs.m10 + lhs.m32 * rhs.m20 + lhs.m33 * rhs.m30; + res.m31 = lhs.m30 * rhs.m01 + lhs.m31 * rhs.m11 + lhs.m32 * rhs.m21 + lhs.m33 * rhs.m31; + res.m32 = lhs.m30 * rhs.m02 + lhs.m31 * rhs.m12 + lhs.m32 * rhs.m22 + lhs.m33 * rhs.m32; + res.m33 = lhs.m30 * rhs.m03 + lhs.m31 * rhs.m13 + lhs.m32 * rhs.m23 + lhs.m33 * rhs.m33; + return res; + } + + public static SDKVector3 operator *(SDKMatrix4x4 lhs, SDKVector3 rhs) + { + SDKVector3 res; + res.x = lhs.m00 * rhs.x + lhs.m01 * rhs.y + lhs.m02 * rhs.z; + res.y = lhs.m10 * rhs.x + lhs.m11 * rhs.y + lhs.m12 * rhs.z; + res.z = lhs.m20 * rhs.x + lhs.m21 * rhs.y + lhs.m22 * rhs.z; + return res; + } + + // Creates a translation matrix. + public static SDKMatrix4x4 Translate(SDKVector3 value) + { + return new SDKMatrix4x4 + { + m00 = 1.0f, + m01 = 0.0f, + m02 = 0.0f, + m03 = value.x, + m10 = 0.0f, + m11 = 1.0f, + m12 = 0.0f, + m13 = value.y, + m20 = 0.0f, + m21 = 0.0f, + m22 = 1.0f, + m23 = value.z, + m30 = 0.0f, + m31 = 0.0f, + m32 = 0.0f, + m33 = 1.0f + }; + } + + // Creates a rotation matrix. + public static SDKMatrix4x4 Rotate(SDKQuaternion value) + { + float qx = value.x; + float qy = value.y; + float qz = value.z; + float qw = value.w; + + return new SDKMatrix4x4 + { + m00 = 1.0f - 2.0f * qy * qy - 2.0f * qz * qz, + m01 = 2.0f * qx * qy - 2.0f * qz * qw, + m02 = 2.0f * qx * qz + 2.0f * qy * qw, + m03 = 0.0f, + m10 = 2.0f * qx * qy + 2.0f * qz * qw, + m11 = 1.0f - 2.0f * qx * qx - 2.0f * qz * qz, + m12 = 2.0f * qy * qz - 2.0f * qx * qw, + m13 = 0.0f, + m20 = 2.0f * qx * qz - 2.0f * qy * qw, + m21 = 2.0f * qy * qz + 2.0f * qx * qw, + m22 = 1.0f - 2.0f * qx * qx - 2.0f * qy * qy, + m23 = 0.0f, + m30 = 0.0f, + m31 = 0.0f, + m32 = 0.0f, + m33 = 1.0f + }; + } + + // Creates a scaling matrix. + public static SDKMatrix4x4 Scale(SDKVector3 value) + { + return new SDKMatrix4x4 + { + m00 = value.x, + m01 = 0.0f, + m02 = 0.0f, + m03 = 0.0f, + m10 = 0.0f, + m11 = value.y, + m12 = 0.0f, + m13 = 0.0f, + m20 = 0.0f, + m21 = 0.0f, + m22 = value.z, + m23 = 0.0f, + m30 = 0.0f, + m31 = 0.0f, + m32 = 0.0f, + m33 = 1.0f + }; + } + + public static SDKMatrix4x4 TRS(SDKVector3 translation, SDKQuaternion rotation, SDKVector3 scale) + { + return Translate(translation) * Rotate(rotation) * Scale(scale); + } + // end delete + + public override string ToString() + { + return +$@"Matrix4x4: +{m00} {m01} {m02} {m03} +{m10} {m11} {m12} {m13} +{m20} {m21} {m22} {m23} +{m30} {m31} {m32} {m33}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKPlane + { + public float distance; + public SDKVector3 normal; + + public static implicit operator SDKPlane(Plane v) + { + return new SDKPlane() + { + distance = v.distance, + normal = v.normal + }; + } + + public static SDKPlane empty { + get { + return new SDKPlane() { distance = 0f, normal = SDKVector3.up }; + } + } + + public override string ToString() + { + return +$@"SDKPlane: +{distance} {normal}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKPriority + { + public sbyte pose; + public sbyte clipPlane; + public sbyte stage; + public sbyte resolution; + public sbyte feature; + public sbyte nearFarAdjustment; + public sbyte groundPlane; + public sbyte reserved2; + + public static SDKPriority empty { + get { + return new SDKPriority() + { + pose = -(sbyte)PRIORITY.GAME, + clipPlane = -(sbyte)PRIORITY.GAME, + stage = -(sbyte)PRIORITY.GAME, + resolution = -(sbyte)PRIORITY.GAME, + feature = -(sbyte)PRIORITY.GAME, + nearFarAdjustment = (sbyte)PRIORITY.GAME, + groundPlane = -(sbyte)PRIORITY.GAME, + reserved2 = -(sbyte)PRIORITY.GAME + }; + } + } + + public override string ToString() + { + return +$@"Priority: +pose: {pose}, clipPlane: {clipPlane}, stage: {stage}, resolution: {resolution}, feature: {feature}, nearFarAdjustment: {nearFarAdjustment}, groundPlane: {groundPlane}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKApplicationOutput + { + public FEATURES supportedFeatures; + public string engineName; + public string engineVersion; + public string applicationName; + public string applicationVersion; + public string xrDeviceName; + public string graphicsAPI; + public string sdkID; + public string sdkVersion; + + public static SDKApplicationOutput empty { + get { + return new SDKApplicationOutput() + { + supportedFeatures = FEATURES.NONE, + engineName = string.Empty, + engineVersion = string.Empty, + applicationName = string.Empty, + applicationVersion = string.Empty, + xrDeviceName = string.Empty, + graphicsAPI = string.Empty, + sdkID = SDKConstants.SDK_ID, + sdkVersion = string.Empty + }; + } + } + + public override string ToString() + { + return +$@"SDKApplicationOutput: +supportedFeatures: {supportedFeatures} +engineName: {engineName} +engineVersion: {engineVersion} +applicationName: {applicationName} +applicationVersion: {applicationVersion} +xrDeviceName: {xrDeviceName} +graphicsAPI: {graphicsAPI} +sdkID: {sdkID} +sdkVersion: {sdkVersion}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKInputFrame + { + public SDKPose pose; + public SDKClipPlane clipPlane; + public SDKTransform stageTransform; + public FEATURES features; + public SDKClipPlane groundClipPlane; + + public ulong frameid; // This is actually the time stamp of this frame - its populated by the bridge at creation time. + public ulong referenceframe; // Use the previous frames frameid to populate this field - it must be set to the correct frame id, or it will fail. + public SDKPriority priority; // this is a mixed field combining flags and priority - the contents of this flag are not yet set in stone + + public static SDKInputFrame empty { + get { + return new SDKInputFrame() + { + pose = SDKPose.empty, + clipPlane = SDKClipPlane.empty, + stageTransform = SDKTransform.empty, + features = FEATURES.NONE, + groundClipPlane = SDKClipPlane.empty, + frameid = 0, + referenceframe = 0, + priority = SDKPriority.empty + }; + } + } + + public void ReleaseControl() + { + priority = SDKPriority.empty; + } + + public void ObtainControl() + { + priority = SDKPriority.empty; + priority.pose = (sbyte)PRIORITY.GAME; + } + + public override string ToString() + { + return +$@"SDKInputFrame: +pose: {pose} +clipPlane: {clipPlane} +stageTransform: {stageTransform} +features: {features} +groundClipPlane: {groundClipPlane} +frameid: {frameid} +referenceframe: {referenceframe} +priority: {priority:X4}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKOutputFrame + { + public RENDERING_PIPELINE renderingPipeline; + public SDKTrackedSpace trackedSpace; + + public static SDKOutputFrame empty { + get { + return new SDKOutputFrame() + { + renderingPipeline = RENDERING_PIPELINE.UNDEFINED, + trackedSpace = SDKTrackedSpace.empty + }; + } + } + + public override string ToString() + { + return +$@"SDKOutputFrame: +renderingPipeline: {renderingPipeline} +trackedSpace: {trackedSpace}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKTrackedSpace + { + public SDKVector3 trackedSpaceWorldPosition; + public SDKQuaternion trackedSpaceWorldRotation; + public SDKVector3 trackedSpaceLocalScale; + public SDKMatrix4x4 trackedSpaceLocalToWorldMatrix; + public SDKMatrix4x4 trackedSpaceWorldToLocalMatrix; + + public static SDKTrackedSpace empty { + get { + return new SDKTrackedSpace() + { + trackedSpaceWorldPosition = SDKVector3.zero, + trackedSpaceWorldRotation = SDKQuaternion.identity, + trackedSpaceLocalScale = SDKVector3.zero, + trackedSpaceLocalToWorldMatrix = SDKMatrix4x4.identity, + trackedSpaceWorldToLocalMatrix = SDKMatrix4x4.identity + }; + } + } + + public override string ToString() + { + return +$@"SDKTrackedSpace: +trackedSpaceWorldPosition: {trackedSpaceWorldPosition} +trackedSpaceWorldRotation: {trackedSpaceWorldRotation} +trackedSpaceLocalScale: {trackedSpaceLocalScale} +trackedSpaceLocalToWorldMatrix: {trackedSpaceLocalToWorldMatrix} +trackedSpaceWorldToLocalMatrix: {trackedSpaceWorldToLocalMatrix}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKTexture + { + public TEXTURE_ID id; + public System.IntPtr texturePtr; + public System.IntPtr SharedHandle; + public TEXTURE_DEVICE device; + public int dummy; + public TEXTURE_TYPE type; + public TEXTURE_FORMAT format; + public TEXTURE_COLOR_SPACE colorSpace; + public int width; + public int height; + + public static SDKTexture empty { + get { + return new SDKTexture() + { + id = TEXTURE_ID.UNDEFINED, + texturePtr = System.IntPtr.Zero, + SharedHandle = System.IntPtr.Zero, + device = TEXTURE_DEVICE.UNDEFINED, + dummy = 0, + type = TEXTURE_TYPE.UNDEFINED, + format = TEXTURE_FORMAT.UNDEFINED, + colorSpace = TEXTURE_COLOR_SPACE.UNDEFINED, + width = 0, + height = 0 + }; + } + } + + public override string ToString() + { + return +$@"SDKTexture: +id: {id} +texturePtr: {texturePtr} +SharedHandle: {SharedHandle} +device: {device} +dummy: {dummy} +type: {type} +format: {format} +colorSpace: {colorSpace} +width: {width} +height: {height}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKTransform + { + public SDKVector3 localPosition; + public SDKQuaternion localRotation; + public SDKVector3 localScale; + + public static SDKTransform empty { + get { + return new SDKTransform() + { + localPosition = SDKVector3.zero, + localRotation = SDKQuaternion.identity, + localScale = SDKVector3.one + }; + } + } + + public override string ToString() + { + return +$@"SDKTransform: +localPosition: {localPosition} +localRotation: {localRotation} +localScale: {localScale}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKClipPlane + { + public SDKMatrix4x4 transform; + public int width; + public int height; + public float tesselation; + + public static SDKClipPlane empty { + get { + return new SDKClipPlane() + { + transform = SDKMatrix4x4.identity, + width = 0, + height = 0, + tesselation = 0 + }; + } + } + + public override string ToString() + { + return +$@"SDKClipPlane: +transform: {transform} +width: {width} +height: {height} +tesselation: {tesselation}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKControllerState + { + public SDKVector3 hmdposition; + public SDKQuaternion hmdrotation; + + public SDKVector3 calibrationcameraposition; + public SDKQuaternion calibrationcamerarotation; + + public SDKVector3 cameraposition; + public SDKQuaternion camerarotation; + + public SDKVector3 leftposition; + public SDKQuaternion leftrotation; + + public SDKVector3 rightposition; + public SDKQuaternion rightrotation; + + public static SDKControllerState empty { + get { + return new SDKControllerState() + { + hmdposition = SDKVector3.zero, + hmdrotation = SDKQuaternion.identity, + calibrationcameraposition = SDKVector3.zero, + calibrationcamerarotation = SDKQuaternion.identity, + cameraposition = SDKVector3.zero, + camerarotation = SDKQuaternion.identity, + leftposition = SDKVector3.zero, + leftrotation = SDKQuaternion.identity, + rightposition = SDKVector3.zero, + rightrotation = SDKQuaternion.identity, + }; + } + } + + public override string ToString() + { + return +$@"SDKControllerState: +hmdposition: {hmdposition} +hmdrotation: {hmdrotation} +calibrationcameraposition: {calibrationcameraposition} +calibrationcamerarotation: {calibrationcamerarotation} +cameraposition: {cameraposition} +camerarotation: {camerarotation} +leftposition: {leftposition} +leftrotation: {leftrotation} +rightposition: {rightposition} +rightrotation: {rightrotation}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SDKPose + { + public SDKMatrix4x4 projectionMatrix; + public SDKVector3 localPosition; + public SDKQuaternion localRotation; + public float verticalFieldOfView; + public float nearClipPlane; + public float farClipPlane; + public int unused0; + public int unused1; + + public static SDKPose empty { + get { + return new SDKPose() + { + projectionMatrix = SDKMatrix4x4.Perspective(90f, 1f, 0.01f, 1000f), + localPosition = SDKVector3.zero, + localRotation = SDKQuaternion.identity, + verticalFieldOfView = 90f, + nearClipPlane = 0.01f, + farClipPlane = 1000f, + }; + } + } + + public override string ToString() + { + return +$@"SDKPose: +projectionMatrix: {projectionMatrix} +localPosition: {localPosition} +localRotation: {localRotation} +verticalFieldOfView: {verticalFieldOfView} +nearClipPlane: {nearClipPlane} +farClipPlane: {farClipPlane}"; + } + } +} \ No newline at end of file diff --git a/Assets/LIV/Scripts/SDKUniversalRender.cs b/Assets/LIV/Scripts/SDKUniversalRender.cs new file mode 100644 index 0000000..fa93625 --- /dev/null +++ b/Assets/LIV/Scripts/SDKUniversalRender.cs @@ -0,0 +1,476 @@ +#if LIV_UNIVERSAL_RENDER +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; + +namespace LIV.SDK.Unity +{ + public partial class SDKRender : System.IDisposable + { + // Renders the clip plane in the foreground texture + private SDKPass _clipPlanePass = null; + // Renders the clipped opaque content in to the foreground texture alpha + private SDKPass _combineAlphaPass = null; + // Captures texture before post-effects + private SDKPass _captureTexturePass = null; + // Renders captured texture + private SDKPass _applyTexturePass = null; + // Renders background and foreground in single render + private SDKPass _optimizedRenderingPass = null; + + private RenderPassEvent _clipPlaneRenderPassEvent = RenderPassEvent.AfterRenderingOpaques; + private RenderPassEvent _addAlphaRenderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; + private RenderPassEvent _captureTextureRenderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; + private RenderPassEvent _applyTextureRenderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; + private RenderPassEvent _optimizedRenderingPassEvent = RenderPassEvent.AfterRendering; + + // Tessellated quad + private Mesh _clipPlaneMesh = null; + // Clear material + private Material _clipPlaneSimpleMaterial = null; + // Transparent material for visual debugging + private Material _clipPlaneSimpleDebugMaterial = null; + // Tessellated height map clear material + private Material _clipPlaneComplexMaterial = null; + // Tessellated height map clear material for visual debugging + private Material _clipPlaneComplexDebugMaterial = null; + private Material _writeOpaqueToAlphaMaterial = null; + private Material _combineAlphaMaterial = null; + private Material _writeMaterial = null; + private Material _forceForwardRenderingMaterial = null; + + private RenderTexture _backgroundRenderTexture = null; + private RenderTexture _foregroundRenderTexture = null; + private RenderTexture _optimizedRenderTexture = null; + private RenderTexture _complexClipPlaneRenderTexture = null; + + private UniversalAdditionalCameraData _universalAdditionalCameraData = null; + private RenderTargetIdentifier _cameraColorTextureIdentifier = new RenderTargetIdentifier("_CameraColorTexture"); + + Material GetClipPlaneMaterial(bool debugClipPlane, bool complexClipPlane, ColorWriteMask colorWriteMask) + { + Material output; + + if (complexClipPlane) + { + output = debugClipPlane ? _clipPlaneComplexDebugMaterial : _clipPlaneComplexMaterial; + output.SetTexture(SDKShaders.LIV_CLIP_PLANE_HEIGHT_MAP_PROPERTY, _complexClipPlaneRenderTexture); + output.SetFloat(SDKShaders.LIV_TESSELLATION_PROPERTY, _inputFrame.clipPlane.tesselation); + } + else + { + output = debugClipPlane ? _clipPlaneSimpleDebugMaterial : _clipPlaneSimpleMaterial; + } + + output.SetInt(SDKShaders.LIV_COLOR_MASK, (int)colorWriteMask); + return output; + } + + Material GetGroundClipPlaneMaterial(bool debugClipPlane, ColorWriteMask colorWriteMask) + { + Material output; + output = debugClipPlane ? _clipPlaneSimpleDebugMaterial : _clipPlaneSimpleMaterial; + output.SetInt(SDKShaders.LIV_COLOR_MASK, (int)colorWriteMask); + return output; + } + + bool useDeferredRendering { + get { + return _cameraInstance.actualRenderingPath == RenderingPath.DeferredLighting || + _cameraInstance.actualRenderingPath == RenderingPath.DeferredShading; + } + } + + bool interlacedRendering { + get { + return SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.INTERLACED_RENDER); + } + } + + bool canRenderBackground { + get { + if (interlacedRendering) + { + // Render only if frame is even + if (Time.frameCount % 2 != 0) return false; + } + return SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.BACKGROUND_RENDER) && _backgroundRenderTexture != null; + } + } + + bool canRenderForeground { + get { + if (interlacedRendering) + { + // Render only if frame is odd + if (Time.frameCount % 2 != 1) return false; + } + return SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.FOREGROUND_RENDER) && _foregroundRenderTexture != null; + } + } + + bool canRenderOptimized { + get { + return SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.OPTIMIZED_RENDER) && _optimizedRenderTexture != null; ; + } + } + + public SDKRender(LIV liv) + { + _liv = liv; + CreateAssets(); + } + + public void Render() + { + UpdateBridgeResolution(); + UpdateBridgeInputFrame(); + SDKUtils.ApplyUserSpaceTransform(this); + UpdateTextures(); + InvokePreRender(); + if (canRenderBackground) RenderBackground(); + if (canRenderForeground) RenderForeground(); + if (canRenderOptimized) RenderOptimized(); + IvokePostRender(); + SDKUtils.CreateBridgeOutputFrame(this); + SDKBridge.IssuePluginEvent(); + } + + // Default render without any special changes + private void RenderBackground() + { + SDKUtils.SetCamera(_cameraInstance, _cameraInstance.transform, _inputFrame, localToWorldMatrix, spectatorLayerMask); + _cameraInstance.targetTexture = _backgroundRenderTexture; + + RenderTexture tempRenderTexture = null; + + bool overridePostProcessing = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.OVERRIDE_POST_PROCESSING); + if (overridePostProcessing) + { + tempRenderTexture = RenderTexture.GetTemporary(_backgroundRenderTexture.width, _backgroundRenderTexture.height, 0, _backgroundRenderTexture.format); +#if UNITY_EDITOR + tempRenderTexture.name = "LIV.TemporaryRenderTexture"; +#endif + + _captureTexturePass.commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, tempRenderTexture); + _applyTexturePass.commandBuffer.Blit(tempRenderTexture, BuiltinRenderTextureType.CurrentActive); + + SDKUniversalRenderFeature.AddPass(_captureTexturePass); + SDKUniversalRenderFeature.AddPass(_applyTexturePass); + } + + SDKShaders.StartRendering(); + SDKShaders.StartBackgroundRendering(); + InvokePreRenderBackground(); + SendTextureToBridge(_backgroundRenderTexture, TEXTURE_ID.BACKGROUND_COLOR_BUFFER_ID); + _cameraInstance.Render(); + InvokePostRenderBackground(); + _cameraInstance.targetTexture = null; + SDKShaders.StopBackgroundRendering(); + SDKShaders.StopRendering(); + + if (overridePostProcessing) + { + _captureTexturePass.commandBuffer.Clear(); + _applyTexturePass.commandBuffer.Clear(); + RenderTexture.ReleaseTemporary(tempRenderTexture); + } + + SDKUniversalRenderFeature.ClearPasses(); + } + + // Extract the image which is in front of our clip plane + // The compositing is heavily relying on the alpha channel, therefore we want to make sure it does + // not get corrupted by the postprocessing or any shader + private void RenderForeground() + { + bool debugClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.DEBUG_CLIP_PLANE); + bool renderComplexClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.COMPLEX_CLIP_PLANE); + bool renderGroundClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.GROUND_CLIP_PLANE); + bool overridePostProcessing = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.OVERRIDE_POST_PROCESSING); + bool fixPostEffectsAlpha = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.FIX_FOREGROUND_ALPHA) | _liv.fixPostEffectsAlpha; + + MonoBehaviour[] behaviours = null; + bool[] wasBehaviourEnabled = null; + if (disableStandardAssets) SDKUtils.DisableStandardAssets(_cameraInstance, ref behaviours, ref wasBehaviourEnabled); + + // Capture camera defaults + CameraClearFlags capturedClearFlags = _cameraInstance.clearFlags; + Color capturedBgColor = _cameraInstance.backgroundColor; + Color capturedFogColor = RenderSettings.fogColor; + + // Make sure that fog does not corrupt alpha channel + RenderSettings.fogColor = new Color(capturedFogColor.r, capturedFogColor.g, capturedFogColor.b, 0f); + SDKUtils.SetCamera(_cameraInstance, _cameraInstance.transform, _inputFrame, localToWorldMatrix, spectatorLayerMask); + _cameraInstance.clearFlags = CameraClearFlags.Color; + _cameraInstance.backgroundColor = Color.clear; + _cameraInstance.targetTexture = _foregroundRenderTexture; + + RenderTexture capturedAlphaRenderTexture = RenderTexture.GetTemporary(_foregroundRenderTexture.width, _foregroundRenderTexture.height, 0, _foregroundRenderTexture.format); +#if UNITY_EDITOR + capturedAlphaRenderTexture.name = "LIV.CapturedAlphaRenderTexture"; +#endif + + // Render opaque pixels into alpha channel + _clipPlanePass.commandBuffer.DrawMesh(_clipPlaneMesh, Matrix4x4.identity, _writeOpaqueToAlphaMaterial, 0, 0); + + // Render clip plane + Matrix4x4 clipPlaneTransform = localToWorldMatrix * (Matrix4x4)_inputFrame.clipPlane.transform; + _clipPlanePass.commandBuffer.DrawMesh(_clipPlaneMesh, clipPlaneTransform, + GetClipPlaneMaterial(debugClipPlane, renderComplexClipPlane, ColorWriteMask.All), 0, 0); + + // Render ground clip plane + if (renderGroundClipPlane) + { + Matrix4x4 groundClipPlaneTransform = localToWorldMatrix * (Matrix4x4)_inputFrame.groundClipPlane.transform; + _clipPlanePass.commandBuffer.DrawMesh(_clipPlaneMesh, groundClipPlaneTransform, + GetGroundClipPlaneMaterial(debugClipPlane, ColorWriteMask.All), 0, 0); + } + + // Copy alpha in to texture + _clipPlanePass.commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, capturedAlphaRenderTexture); + _clipPlanePass.commandBuffer.SetRenderTarget(_cameraColorTextureIdentifier); + SDKUniversalRenderFeature.AddPass(_clipPlanePass); + + // Fix alpha corruption by post processing + RenderTexture tempRenderTexture = null; + if (overridePostProcessing || fixPostEffectsAlpha) + { + tempRenderTexture = RenderTexture.GetTemporary(_foregroundRenderTexture.width, _foregroundRenderTexture.height, 0, _foregroundRenderTexture.format); +#if UNITY_EDITOR + tempRenderTexture.name = "LIV.TemporaryRenderTexture"; +#endif + _captureTexturePass.commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, tempRenderTexture); + SDKUniversalRenderFeature.AddPass(_captureTexturePass); + + _writeMaterial.SetInt(SDKShaders.LIV_COLOR_MASK, overridePostProcessing ? (int)ColorWriteMask.All : (int)ColorWriteMask.Alpha); + _applyTexturePass.commandBuffer.Blit(tempRenderTexture, BuiltinRenderTextureType.CurrentActive, _writeMaterial); + SDKUniversalRenderFeature.AddPass(_applyTexturePass); + } + + // Combine captured alpha with result alpha + _combineAlphaMaterial.SetInt(SDKShaders.LIV_COLOR_MASK, (int)ColorWriteMask.Alpha); + _combineAlphaPass.commandBuffer.Blit(capturedAlphaRenderTexture, BuiltinRenderTextureType.CurrentActive, _combineAlphaMaterial); + SDKUniversalRenderFeature.AddPass(_combineAlphaPass); + + if (useDeferredRendering) SDKUtils.ForceForwardRendering(cameraInstance, _clipPlaneMesh, _forceForwardRenderingMaterial); + + SDKShaders.StartRendering(); + SDKShaders.StartForegroundRendering(); + InvokePreRenderForeground(); + SendTextureToBridge(_foregroundRenderTexture, TEXTURE_ID.FOREGROUND_COLOR_BUFFER_ID); + _cameraInstance.Render(); + InvokePostRenderForeground(); + _cameraInstance.targetTexture = null; + SDKShaders.StopForegroundRendering(); + SDKShaders.StopRendering(); + + if (overridePostProcessing || fixPostEffectsAlpha) + { + _captureTexturePass.commandBuffer.Clear(); + _applyTexturePass.commandBuffer.Clear(); + + RenderTexture.ReleaseTemporary(tempRenderTexture); + } + + RenderTexture.ReleaseTemporary(capturedAlphaRenderTexture); + + _clipPlanePass.commandBuffer.Clear(); + _combineAlphaPass.commandBuffer.Clear(); + + SDKUniversalRenderFeature.ClearPasses(); + + // Revert camera defaults + _cameraInstance.clearFlags = capturedClearFlags; + _cameraInstance.backgroundColor = capturedBgColor; + RenderSettings.fogColor = capturedFogColor; + + SDKUtils.RestoreStandardAssets(ref behaviours, ref wasBehaviourEnabled); + } + + // Renders a single camera in a single texture with occlusion only from opaque objects. + // This is the most performant option for mixed reality. + // It does not support any transparency in the foreground layer. + private void RenderOptimized() + { + bool debugClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.DEBUG_CLIP_PLANE); + bool renderComplexClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.COMPLEX_CLIP_PLANE); + bool renderGroundClipPlane = SDKUtils.FeatureEnabled(inputFrame.features, FEATURES.GROUND_CLIP_PLANE); + + SDKUtils.SetCamera(_cameraInstance, _cameraInstance.transform, _inputFrame, localToWorldMatrix, spectatorLayerMask); + _cameraInstance.targetTexture = _optimizedRenderTexture; + + // Clear alpha channel + _writeMaterial.SetInt(SDKShaders.LIV_COLOR_MASK, (int)ColorWriteMask.Alpha); + _optimizedRenderingPass.commandBuffer.Blit(BuiltinRenderTextureType.None, BuiltinRenderTextureType.CurrentActive, _writeMaterial); + + // Render opaque pixels into alpha channel + _writeOpaqueToAlphaMaterial.SetInt(SDKShaders.LIV_COLOR_MASK, (int)ColorWriteMask.Alpha); + _optimizedRenderingPass.commandBuffer.DrawMesh(_clipPlaneMesh, Matrix4x4.identity, _writeOpaqueToAlphaMaterial, 0, 0); + + // Render clip plane + Matrix4x4 clipPlaneTransform = localToWorldMatrix * (Matrix4x4)_inputFrame.clipPlane.transform; + _optimizedRenderingPass.commandBuffer.DrawMesh(_clipPlaneMesh, clipPlaneTransform, + GetClipPlaneMaterial(debugClipPlane, renderComplexClipPlane, ColorWriteMask.Alpha), 0, 0); + + // Render ground clip plane + if (renderGroundClipPlane) + { + Matrix4x4 groundClipPlaneTransform = localToWorldMatrix * (Matrix4x4)_inputFrame.groundClipPlane.transform; + _optimizedRenderingPass.commandBuffer.DrawMesh(_clipPlaneMesh, groundClipPlaneTransform, + GetGroundClipPlaneMaterial(debugClipPlane, ColorWriteMask.Alpha), 0, 0); + } + + SDKUniversalRenderFeature.AddPass(_optimizedRenderingPass); + + // TODO: this is just proprietary + SDKShaders.StartRendering(); + SDKShaders.StartBackgroundRendering(); + InvokePreRenderBackground(); + SendTextureToBridge(_optimizedRenderTexture, TEXTURE_ID.OPTIMIZED_COLOR_BUFFER_ID); + _cameraInstance.Render(); + InvokePostRenderBackground(); + _cameraInstance.targetTexture = null; + SDKShaders.StopBackgroundRendering(); + SDKShaders.StopRendering(); + + _optimizedRenderingPass.commandBuffer.Clear(); + SDKUniversalRenderFeature.ClearPasses(); + } + + private void CreateAssets() + { + bool cameraReferenceEnabled = cameraReference.enabled; + if (cameraReferenceEnabled) + { + cameraReference.enabled = false; + } + bool cameraReferenceActive = cameraReference.gameObject.activeSelf; + if (cameraReferenceActive) + { + cameraReference.gameObject.SetActive(false); + } + + GameObject cloneGO = (GameObject)Object.Instantiate(cameraReference.gameObject, _liv.stage); + _cameraInstance = (Camera)cloneGO.GetComponent("Camera"); + + SDKUtils.CleanCameraBehaviours(_cameraInstance, _liv.excludeBehaviours); + + if (cameraReferenceActive != cameraReference.gameObject.activeSelf) + { + cameraReference.gameObject.SetActive(cameraReferenceActive); + } + if (cameraReferenceEnabled != cameraReference.enabled) + { + cameraReference.enabled = cameraReferenceEnabled; + } + + _cameraInstance.name = "LIV Camera"; + if (_cameraInstance.tag == "MainCamera") + { + _cameraInstance.tag = "Untagged"; + } + + _cameraInstance.transform.localScale = Vector3.one; + _cameraInstance.rect = new Rect(0, 0, 1, 1); + _cameraInstance.depth = 0; +#if UNITY_5_4_OR_NEWER + _cameraInstance.stereoTargetEye = StereoTargetEyeMask.None; +#endif +#if UNITY_5_6_OR_NEWER + _cameraInstance.allowMSAA = false; +#endif + _cameraInstance.enabled = false; + _cameraInstance.gameObject.SetActive(true); + _universalAdditionalCameraData = _cameraInstance.GetComponent(); + + _clipPlaneMesh = new Mesh(); + SDKUtils.CreateClipPlane(_clipPlaneMesh, 10, 10, true, 1000f); + _clipPlaneSimpleMaterial = new Material(Shader.Find(SDKShaders.LIV_CLIP_PLANE_SIMPLE_SHADER)); + _clipPlaneSimpleDebugMaterial = new Material(Shader.Find(SDKShaders.LIV_CLIP_PLANE_SIMPLE_DEBUG_SHADER)); + _clipPlaneComplexMaterial = new Material(Shader.Find(SDKShaders.LIV_CLIP_PLANE_COMPLEX_SHADER)); + _clipPlaneComplexDebugMaterial = new Material(Shader.Find(SDKShaders.LIV_CLIP_PLANE_COMPLEX_DEBUG_SHADER)); + _writeOpaqueToAlphaMaterial = new Material(Shader.Find(SDKShaders.LIV_WRITE_OPAQUE_TO_ALPHA_SHADER)); + _combineAlphaMaterial = new Material(Shader.Find(SDKShaders.LIV_COMBINE_ALPHA_SHADER)); + _writeMaterial = new Material(Shader.Find(SDKShaders.LIV_WRITE_SHADER)); + _forceForwardRenderingMaterial = new Material(Shader.Find(SDKShaders.LIV_FORCE_FORWARD_RENDERING_SHADER)); + + _clipPlanePass = new SDKPass(); + _clipPlanePass.renderPassEvent = _clipPlaneRenderPassEvent; + _clipPlanePass.commandBuffer = new CommandBuffer(); + + _combineAlphaPass = new SDKPass(); + _combineAlphaPass.renderPassEvent = _addAlphaRenderPassEvent; + _combineAlphaPass.commandBuffer = new CommandBuffer(); + + _captureTexturePass = new SDKPass(); + _captureTexturePass.renderPassEvent = _captureTextureRenderPassEvent; + _captureTexturePass.commandBuffer = new CommandBuffer(); + + _applyTexturePass = new SDKPass(); + _applyTexturePass.renderPassEvent = _applyTextureRenderPassEvent; + _applyTexturePass.commandBuffer = new CommandBuffer(); + + _optimizedRenderingPass = new SDKPass(); + _optimizedRenderingPass.renderPassEvent = _optimizedRenderingPassEvent; + _optimizedRenderingPass.commandBuffer = new CommandBuffer(); + + _universalAdditionalCameraData.antialiasing = AntialiasingMode.None; + _universalAdditionalCameraData.antialiasingQuality = AntialiasingQuality.Low; + _universalAdditionalCameraData.dithering = false; + +#if UNITY_EDITOR + _clipPlaneMesh.name = "LIV.clipPlane"; + _clipPlaneSimpleMaterial.name = "LIV.clipPlaneSimple"; + _clipPlaneSimpleDebugMaterial.name = "LIV.clipPlaneSimpleDebug"; + _clipPlaneComplexMaterial.name = "LIV.clipPlaneComplex"; + _clipPlaneComplexDebugMaterial.name = "LIV.clipPlaneComplexDebug"; + _writeOpaqueToAlphaMaterial.name = "LIV.writeOpaqueToAlpha"; + _combineAlphaMaterial.name = "LIV.combineAlpha"; + _writeMaterial.name = "LIV.write"; + _forceForwardRenderingMaterial.name = "LIV.forceForwardRendering"; + _clipPlanePass.commandBuffer.name = "LIV.renderClipPlanes"; + _combineAlphaPass.commandBuffer.name = "LIV.foregroundCombineAlpha"; + _captureTexturePass.commandBuffer.name = "LIV.captureTexture"; + _applyTexturePass.commandBuffer.name = "LIV.applyTexture"; + _optimizedRenderingPass.commandBuffer.name = "LIV.optimizedRendering"; +#endif + } + + private void DestroyAssets() + { + if (_cameraInstance != null) + { + Object.Destroy(_cameraInstance.gameObject); + _cameraInstance = null; + } + + SDKUtils.DestroyObject(ref _clipPlaneMesh); + SDKUtils.DestroyObject(ref _clipPlaneSimpleMaterial); + SDKUtils.DestroyObject(ref _clipPlaneSimpleDebugMaterial); + SDKUtils.DestroyObject(ref _clipPlaneComplexMaterial); + SDKUtils.DestroyObject(ref _clipPlaneComplexDebugMaterial); + SDKUtils.DestroyObject(ref _writeOpaqueToAlphaMaterial); + SDKUtils.DestroyObject(ref _combineAlphaMaterial); + SDKUtils.DestroyObject(ref _writeMaterial); + SDKUtils.DestroyObject(ref _forceForwardRenderingMaterial); + + SDKUtils.DisposeObject(ref _clipPlanePass.commandBuffer); + SDKUtils.DisposeObject(ref _combineAlphaPass.commandBuffer); + SDKUtils.DisposeObject(ref _captureTexturePass.commandBuffer); + SDKUtils.DisposeObject(ref _applyTexturePass.commandBuffer); + SDKUtils.DisposeObject(ref _optimizedRenderingPass.commandBuffer); + } + + public void Dispose() + { + ReleaseBridgePoseControl(); + DestroyAssets(); + SDKUtils.DestroyTexture(ref _backgroundRenderTexture); + SDKUtils.DestroyTexture(ref _foregroundRenderTexture); + SDKUtils.DestroyTexture(ref _optimizedRenderTexture); + SDKUtils.DestroyTexture(ref _complexClipPlaneRenderTexture); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/LIV/Scripts/SDKUniversalRenderFeature.cs b/Assets/LIV/Scripts/SDKUniversalRenderFeature.cs new file mode 100644 index 0000000..aa9085f --- /dev/null +++ b/Assets/LIV/Scripts/SDKUniversalRenderFeature.cs @@ -0,0 +1,49 @@ +#if LIV_UNIVERSAL_RENDER +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; +using System.Collections; +using System.Collections.Generic; + +namespace LIV.SDK.Unity +{ + public class SDKPass : ScriptableRenderPass + { + public CommandBuffer commandBuffer; + + public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) + { + context.ExecuteCommandBuffer(commandBuffer); + } + } + + public class SDKUniversalRenderFeature : ScriptableRendererFeature + { + static List passes = new List(); + + public static void AddPass(SDKPass pass) + { + passes.Add(pass); + } + + public static void ClearPasses() + { + passes.Clear(); + } + + public override void Create() + { + + } + + public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) + { + while (passes.Count > 0) + { + renderer.EnqueuePass(passes[0]); + passes.RemoveAt(0); + } + } + } +} +#endif \ No newline at end of file diff --git a/Assets/LIV/Scripts/SDKUtils.cs b/Assets/LIV/Scripts/SDKUtils.cs new file mode 100644 index 0000000..171b626 --- /dev/null +++ b/Assets/LIV/Scripts/SDKUtils.cs @@ -0,0 +1,343 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace LIV.SDK.Unity +{ + public static class SDKUtils + { + public static void CreateClipPlane(Mesh mesh, int resX, int resY, bool useQuads, float skirtLength) + { + int vertexCount = (resX + 1) * (resY + 1); + int triangleCount = useQuads ? (resX * resY * 4) : (resX * resY * 2 * 3); + Vector3[] v = new Vector3[vertexCount]; + Vector2[] uv = new Vector2[vertexCount]; + int[] t = new int[triangleCount]; + + float hWidth = 0.5f; + float hHeight = 0.5f; + + int resX1 = resX + 1; + int resY1 = resY + 1; + + for (int y = 0; y < resY1; y++) + { + for (int x = 0; x < resX1; x++) + { + int vi = y * resX1 + x; + float uvx = (float)x / (float)resX; + float uvy = (float)y / (float)resY; + float skirtX = (x == 0 || x == resX) ? skirtLength : 1f; + float skirtY = (y == 0 || y == resY) ? skirtLength : 1f; + v[vi] = new Vector2((-hWidth + uvx) * skirtX, (-hHeight + uvy) * skirtY); + uv[vi] = new Vector2(Mathf.InverseLerp(1, resX - 1, x), Mathf.InverseLerp(1, resY - 1, y)); + } + } + + mesh.Clear(); + mesh.vertices = v; + mesh.uv = uv; + mesh.bounds = new Bounds(Vector3.zero, Vector3.one * float.MaxValue); + + { + int faces = resX * resY; + int vi = 0; + int ti = 0; + + if (useQuads) + { + for (int i = 0; i < faces; i++) + { + vi = (i / resX) * resX1 + (i % resX); + t[ti++] = vi + 1; + t[ti++] = vi; + t[ti++] = vi + 1 + resX; + t[ti++] = vi + 2 + resX; + } + mesh.SetIndices(t, MeshTopology.Quads, 0); + } + else + { + for (int i = 0; i < faces; i++) + { + vi = (i / resX) * resX1 + (i % resX); + + t[ti++] = vi + 2 + resX; + t[ti++] = vi + 1; + t[ti++] = vi; + + t[ti++] = vi + 1 + resX; + t[ti++] = vi + 2 + resX; + t[ti++] = vi; + } + mesh.SetIndices(t, MeshTopology.Triangles, 0); + } + } + } + + public static RenderTextureReadWrite GetReadWriteFromColorSpace(TEXTURE_COLOR_SPACE colorSpace) + { + switch (colorSpace) + { + case TEXTURE_COLOR_SPACE.LINEAR: + return RenderTextureReadWrite.Linear; + case TEXTURE_COLOR_SPACE.SRGB: + return RenderTextureReadWrite.sRGB; + default: + return RenderTextureReadWrite.Default; + } + } + + public static TEXTURE_COLOR_SPACE GetDefaultColorSpace { + get { + switch (QualitySettings.activeColorSpace) + { + case UnityEngine.ColorSpace.Gamma: + return TEXTURE_COLOR_SPACE.SRGB; + case UnityEngine.ColorSpace.Linear: + return TEXTURE_COLOR_SPACE.LINEAR; + + } + return TEXTURE_COLOR_SPACE.UNDEFINED; + } + } + + public static TEXTURE_COLOR_SPACE GetColorSpace(RenderTexture renderTexture) + { + if (renderTexture == null) return TEXTURE_COLOR_SPACE.UNDEFINED; + if (renderTexture.sRGB) return TEXTURE_COLOR_SPACE.SRGB; + return TEXTURE_COLOR_SPACE.LINEAR; + } + + public static RENDERING_PIPELINE GetRenderingPipeline(RenderingPath renderingPath) + { + switch (renderingPath) + { + case RenderingPath.DeferredLighting: + return RENDERING_PIPELINE.DEFERRED; + case RenderingPath.DeferredShading: + return RENDERING_PIPELINE.DEFERRED; + case RenderingPath.Forward: + return RENDERING_PIPELINE.FORWARD; + case RenderingPath.VertexLit: + return RENDERING_PIPELINE.VERTEX_LIT; + default: + return RENDERING_PIPELINE.UNDEFINED; + } + } + + public static TEXTURE_DEVICE GetDevice() + { + switch (SystemInfo.graphicsDeviceType) + { + case UnityEngine.Rendering.GraphicsDeviceType.Direct3D11: + case UnityEngine.Rendering.GraphicsDeviceType.Direct3D12: + return TEXTURE_DEVICE.DIRECTX; + case UnityEngine.Rendering.GraphicsDeviceType.Vulkan: + return TEXTURE_DEVICE.VULKAN; + case UnityEngine.Rendering.GraphicsDeviceType.Metal: + return TEXTURE_DEVICE.METAL; + case UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore: + return TEXTURE_DEVICE.OPENGL; + default: + return TEXTURE_DEVICE.UNDEFINED; + } + } + + public static bool ContainsFlag(ulong flags, ulong flag) + { + return (flags & flag) != 0; + } + + public static ulong SetFlag(ulong flags, ulong flag, bool enabled) + { + if (enabled) + { + return flags | flag; + } + else + { + return flags & (~flag); + } + } + + public static void GetCameraPositionAndRotation(SDKPose pose, Matrix4x4 originLocalToWorldMatrix, out Vector3 position, out Quaternion rotation) + { + position = originLocalToWorldMatrix.MultiplyPoint(pose.localPosition); + rotation = RotateQuaternionByMatrix(originLocalToWorldMatrix, pose.localRotation); + } + + public static void CleanCameraBehaviours(Camera camera, string[] excludeBehaviours) + { + // Remove all children from camera clone. + foreach (Transform child in camera.transform) + { + Object.Destroy(child.gameObject); + } + + if (excludeBehaviours == null) return; + for (int i = 0; i < excludeBehaviours.Length; i++) + { + Object.Destroy(camera.GetComponent(excludeBehaviours[i])); + } + } + + public static void SetCamera(Camera camera, Transform cameraTransform, SDKInputFrame inputFrame, Matrix4x4 originLocalToWorldMatrix, int layerMask) + { + Vector3 worldPosition = Vector3.zero; + Quaternion worldRotation = Quaternion.identity; + float verticalFieldOfView = inputFrame.pose.verticalFieldOfView; + float nearClipPlane = inputFrame.pose.nearClipPlane; + float farClipPlane = inputFrame.pose.farClipPlane; + Matrix4x4 projectionMatrix = inputFrame.pose.projectionMatrix; + + GetCameraPositionAndRotation(inputFrame.pose, originLocalToWorldMatrix, out worldPosition, out worldRotation); + + cameraTransform.position = worldPosition; + cameraTransform.rotation = worldRotation; + camera.fieldOfView = verticalFieldOfView; + camera.nearClipPlane = nearClipPlane; + camera.farClipPlane = farClipPlane; + camera.projectionMatrix = projectionMatrix; + camera.cullingMask = layerMask; + } + + public static Quaternion RotateQuaternionByMatrix(Matrix4x4 matrix, Quaternion rotation) + { + return Quaternion.LookRotation( + matrix.MultiplyVector(Vector3.forward), + matrix.MultiplyVector(Vector3.up) + ) * rotation; + } + + public static SDKTrackedSpace GetTrackedSpace(Transform transform) + { + if (transform == null) return SDKTrackedSpace.empty; + return new SDKTrackedSpace + { + trackedSpaceWorldPosition = transform.position, + trackedSpaceWorldRotation = transform.rotation, + trackedSpaceLocalScale = transform.localScale, + trackedSpaceLocalToWorldMatrix = transform.localToWorldMatrix, + trackedSpaceWorldToLocalMatrix = transform.worldToLocalMatrix, + }; + } + + public static bool DestroyObject(ref T reference) where T : UnityEngine.Object + { + if (reference == null) return false; + Object.Destroy(reference); + reference = default(T); + return true; + } + + public static bool DisposeObject(ref T reference) where T : System.IDisposable + { + if (reference == null) return false; + reference.Dispose(); + reference = default(T); + return true; + } + + public static bool CreateTexture(ref RenderTexture renderTexture, int width, int height, int depth, RenderTextureFormat format) + { + DestroyTexture(ref renderTexture); + if (width <= 0 || height <= 0) + { + Debug.LogError("LIV: Unable to create render texture. Texture dimension must be higher than zero."); + return false; + } + + renderTexture = new RenderTexture(width, height, depth, format) + { + antiAliasing = 1, + wrapMode = TextureWrapMode.Clamp, + useMipMap = false, + anisoLevel = 0 + }; + + if (!renderTexture.Create()) + { + Debug.LogError("LIV: Unable to create render texture."); + return false; + } + + return true; + } + + public static void DestroyTexture(ref RenderTexture _renderTexture) + { + if (_renderTexture == null) return; + if (_renderTexture.IsCreated()) + { + _renderTexture.Release(); + } + _renderTexture = null; + } + + public static void ApplyUserSpaceTransform(SDKRender render) + { + if (render.stageTransform == null) return; + render.stageTransform.localPosition = render.inputFrame.stageTransform.localPosition; + render.stageTransform.localRotation = render.inputFrame.stageTransform.localRotation; + render.stageTransform.localScale = render.inputFrame.stageTransform.localScale; + } + + public static void CreateBridgeOutputFrame(SDKRender render) + { + RENDERING_PIPELINE renderingPipeline = RENDERING_PIPELINE.UNDEFINED; +#if LIV_UNIVERSAL_RENDER + renderingPipeline = RENDERING_PIPELINE.UNIVERSAL; +#else + if(render.cameraInstance != null) + { + renderingPipeline = SDKUtils.GetRenderingPipeline(render.cameraInstance.actualRenderingPath); + } +#endif + SDKBridge.CreateFrame(new SDKOutputFrame() + { + renderingPipeline = renderingPipeline, + trackedSpace = SDKUtils.GetTrackedSpace(render.stageTransform == null ? render.stage : render.stageTransform) + }); + } + + public static bool FeatureEnabled(FEATURES features, FEATURES feature) + { + return SDKUtils.ContainsFlag((ulong)features, (ulong)feature); + } + + // Disable standard assets if required. + public static void DisableStandardAssets(Camera cameraInstance, ref MonoBehaviour[] behaviours, ref bool[] wasBehaviourEnabled) + { + behaviours = null; + wasBehaviourEnabled = null; + behaviours = cameraInstance.gameObject.GetComponents(); + wasBehaviourEnabled = new bool[behaviours.Length]; + for (var i = 0; i < behaviours.Length; i++) + { + var behaviour = behaviours[i]; + // generates garbage + if (behaviour.enabled && behaviour.GetType().ToString().StartsWith("UnityStandardAssets.")) + { + behaviour.enabled = false; + wasBehaviourEnabled[i] = true; + } + } + } + + // Restore disabled behaviours. + public static void RestoreStandardAssets(ref MonoBehaviour[] behaviours, ref bool[] wasBehaviourEnabled) + { + if (behaviours != null) + for (var i = 0; i < behaviours.Length; i++) + if (wasBehaviourEnabled[i]) + behaviours[i].enabled = true; + } + + public static void ForceForwardRendering(Camera cameraInstance, Mesh clipPlaneMesh, Material forceForwardRenderingMaterial) + { + Matrix4x4 forceForwardRenderingMatrix = cameraInstance.transform.localToWorldMatrix * Matrix4x4.TRS(Vector3.forward * (cameraInstance.nearClipPlane + 0.1f), Quaternion.identity, Vector3.one); + Graphics.DrawMesh(clipPlaneMesh, forceForwardRenderingMatrix, forceForwardRenderingMaterial, 0, cameraInstance, 0, new MaterialPropertyBlock(), false, false, false); + } + } +} \ No newline at end of file diff --git a/Assets/Scenes/MainScene.unity b/Assets/Scenes/MainScene.unity index 235cd16..b39bcdd 100644 --- a/Assets/Scenes/MainScene.unity +++ b/Assets/Scenes/MainScene.unity @@ -17925,6 +17925,65 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 8049113947432375598, guid: 4cbf0dbca1ca1a5499c4b7806ab6aac5, type: 3} +--- !u!1 &1698363238 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1698363240} + - component: {fileID: 1698363239} + m_Layer: 0 + m_Name: LIV + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1698363239 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1698363238} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 419ad76f2ffc722478bc67f61f9c32e7, type: 3} + m_Name: + m_EditorClassIdentifier: + _stage: {fileID: 1270359727} + _stageTransform: {fileID: 0} + _HMDCamera: {fileID: 1249534346} + _MRCameraPrefab: {fileID: 0} + _disableStandardAssets: 0 + _spectatorLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + _excludeBehaviours: + - AudioListener + - Collider + - SteamVR_Camera + - SteamVR_Fade + - SteamVR_ExternalCamera + _fixPostEffectsAlpha: 1 +--- !u!4 &1698363240 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1698363238} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 11 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1701657063 GameObject: m_ObjectHideFlags: 0 @@ -18951,6 +19010,7 @@ MonoBehaviour: type: 3} m_CancelAction: {fileID: 2387711382375263438, guid: c348712bda248c246b8c49b3db54643f, type: 3} + m_EnableBuiltinActionsAsFallback: 1 m_EnableGamepadInput: 1 m_EnableJoystickInput: 1 m_HorizontalAxis: Horizontal diff --git a/Assets/Settings/URP-Performant-Renderer.asset b/Assets/Settings/URP-Performant-Renderer.asset index d056a66..1b556f5 100644 --- a/Assets/Settings/URP-Performant-Renderer.asset +++ b/Assets/Settings/URP-Performant-Renderer.asset @@ -1,5 +1,18 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!114 &-2850737546700727566 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8e67cb7969c50a9428e7b47bea58abff, type: 3} + m_Name: SDKUniversalRenderFeature + m_EditorClassIdentifier: + m_Active: 1 --- !u!114 &11400000 MonoBehaviour: m_ObjectHideFlags: 0 @@ -15,8 +28,9 @@ MonoBehaviour: debugShaders: debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3} - m_RendererFeatures: [] - m_RendererFeatureMap: + m_RendererFeatures: + - {fileID: -2850737546700727566} + m_RendererFeatureMap: f2da081d4e2570d8 m_UseNativeRenderPass: 0 postProcessData: {fileID: 0} xrSystemData: {fileID: 11400000, guid: 60e1133243b97e347b653163a8c01b64, type: 2} diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 933f132..f14ec93 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -806,7 +806,8 @@ PlayerSettings: webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLDecompressionFallback: 0 - scriptingDefineSymbols: {} + scriptingDefineSymbols: + Standalone: LIV_UNIVERSAL_RENDER additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: