using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using UnityEngine.Assertions; namespace UnityEngine.Rendering.PostProcessing { using SceneManagement; using UnityObject = UnityEngine.Object; public static class RuntimeUtilities { #region Textures static Texture2D m_WhiteTexture; public static Texture2D whiteTexture { get { if (m_WhiteTexture == null) { m_WhiteTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); m_WhiteTexture.SetPixel(0, 0, Color.white); m_WhiteTexture.Apply(); } return m_WhiteTexture; } } static Texture2D m_BlackTexture; public static Texture2D blackTexture { get { if (m_BlackTexture == null) { m_BlackTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); m_BlackTexture.SetPixel(0, 0, Color.black); m_BlackTexture.Apply(); } return m_BlackTexture; } } static Texture2D m_TransparentTexture; public static Texture2D transparentTexture { get { if (m_TransparentTexture == null) { m_TransparentTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); m_TransparentTexture.SetPixel(0, 0, Color.clear); m_TransparentTexture.Apply(); } return m_TransparentTexture; } } #endregion #region Rendering static Mesh s_FullscreenTriangle; public static Mesh fullscreenTriangle { get { if (s_FullscreenTriangle != null) return s_FullscreenTriangle; s_FullscreenTriangle = new Mesh { name = "Fullscreen Triangle" }; // Because we have to support older platforms (GLES2/3, DX9 etc) we can't do all of // this directly in the vertex shader using vertex ids :( s_FullscreenTriangle.SetVertices(new List { new Vector3(-1f, -1f, 0f), new Vector3(-1f, 3f, 0f), new Vector3( 3f, -1f, 0f) }); s_FullscreenTriangle.SetIndices(new [] { 0, 1, 2 }, MeshTopology.Triangles, 0, false); s_FullscreenTriangle.UploadMeshData(false); return s_FullscreenTriangle; } } static Material s_CopyStdMaterial; public static Material copyStdMaterial { get { if (s_CopyStdMaterial != null) return s_CopyStdMaterial; var shader = Shader.Find("Hidden/PostProcessing/CopyStd"); s_CopyStdMaterial = new Material(shader) { name = "PostProcess - CopyStd", hideFlags = HideFlags.HideAndDontSave }; return s_CopyStdMaterial; } } static Material s_CopyMaterial; public static Material copyMaterial { get { if (s_CopyMaterial != null) return s_CopyMaterial; var shader = Shader.Find("Hidden/PostProcessing/Copy"); s_CopyMaterial = new Material(shader) { name = "PostProcess - Copy", hideFlags = HideFlags.HideAndDontSave }; return s_CopyMaterial; } } static PropertySheet s_CopySheet; public static PropertySheet copySheet { get { if (s_CopySheet == null) s_CopySheet = new PropertySheet(copyMaterial); return s_CopySheet; } } // Use a custom blit method to draw a fullscreen triangle instead of a fullscreen quad // https://michaldrobot.com/2014/04/01/gcn-execution-patterns-in-full-screen-passes/ public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, bool clear = false) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); cmd.SetRenderTarget(destination); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, copyMaterial, 0, 0); } public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, PropertySheet propertySheet, int pass, bool clear = false) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); cmd.SetRenderTarget(destination); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); } public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, RenderTargetIdentifier depth, PropertySheet propertySheet, int pass, bool clear = false) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); cmd.SetRenderTarget(destination, depth); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); } public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier[] destinations, RenderTargetIdentifier depth, PropertySheet propertySheet, int pass, bool clear = false) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); cmd.SetRenderTarget(destinations, depth); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); } public static void BlitFullscreenTriangle(Texture source, RenderTexture destination, Material material, int pass) { var oldRt = RenderTexture.active; material.SetPass(pass); if (source != null) material.SetTexture(ShaderIDs.MainTex, source); Graphics.SetRenderTarget(destination); Graphics.DrawMeshNow(fullscreenTriangle, Matrix4x4.identity); RenderTexture.active = oldRt; } // Fast basic copy texture if available, falls back to blit copy if not // Assumes that both textures have the exact same type and format public static void CopyTexture(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination) { if (SystemInfo.copyTextureSupport > CopyTextureSupport.None) { cmd.CopyTexture(source, destination); return; } cmd.BlitFullscreenTriangle(source, destination); } // TODO: Generalize the GetTemporaryRT and Blit commands in order to support // RT Arrays for Stereo Instancing/MultiView #endregion #region Unity specifics & misc methods public static bool scriptableRenderPipelineActive { get { return GraphicsSettings.renderPipelineAsset != null; } // 5.6+ only } #if UNITY_EDITOR public static bool isSinglePassStereoSelected { get { return UnityEditor.PlayerSettings.virtualRealitySupported && UnityEditor.PlayerSettings.stereoRenderingPath == UnityEditor.StereoRenderingPath.SinglePass; } } #endif // TODO: Check for SPSR support at runtime public static bool isSinglePassStereoEnabled { get { #if UNITY_EDITOR return isSinglePassStereoSelected && Application.isPlaying; #elif UNITY_2017_2_OR_NEWER return UnityEngine.XR.XRSettings.eyeTextureDesc.vrUsage == VRTextureUsage.TwoEyes; #else return false; #endif } } public static bool isVREnabled { get { #if UNITY_EDITOR return UnityEditor.PlayerSettings.virtualRealitySupported; #elif UNITY_XBOXONE return false; #elif UNITY_2017_2_OR_NEWER return UnityEngine.XR.XRSettings.enabled; #elif UNITY_5_6_OR_NEWER return UnityEngine.VR.VRSettings.enabled; #endif } } public static bool isAndroidOpenGL { get { return Application.platform == RuntimePlatform.Android && SystemInfo.graphicsDeviceType != GraphicsDeviceType.Vulkan; } } public static void Destroy(UnityObject obj) { if (obj != null) { #if UNITY_EDITOR if (Application.isPlaying) UnityObject.Destroy(obj); else UnityObject.DestroyImmediate(obj); #else UnityObject.Destroy(obj); #endif } } public static bool isLinearColorSpace { get { return QualitySettings.activeColorSpace == ColorSpace.Linear; } } public static bool IsResolvedDepthAvailable(Camera camera) { // AFAIK resolved depth is only available on D3D11/12 via BuiltinRenderTextureType.ResolvedDepth // TODO: Is there more proper way to determine this? What about SRPs? var gtype = SystemInfo.graphicsDeviceType; return camera.actualRenderingPath == RenderingPath.DeferredShading && (gtype == GraphicsDeviceType.Direct3D11 || gtype == GraphicsDeviceType.Direct3D12 || gtype == GraphicsDeviceType.XboxOne); } public static void DestroyProfile(PostProcessProfile profile, bool destroyEffects) { if (destroyEffects) { foreach (var effect in profile.settings) Destroy(effect); } Destroy(profile); } public static void DestroyVolume(PostProcessVolume volume, bool destroySharedProfile) { if (destroySharedProfile) DestroyProfile(volume.sharedProfile, true); Destroy(volume); } // Returns ALL scene objects in the hierarchy, included inactive objects // Beware, this method will be slow for big scenes public static IEnumerable GetAllSceneObjects() where T : Component { var queue = new Queue(); var roots = SceneManager.GetActiveScene().GetRootGameObjects(); foreach (var root in roots) { queue.Enqueue(root.transform); var comp = root.GetComponent(); if (comp != null) yield return comp; } while (queue.Count > 0) { foreach (Transform child in queue.Dequeue()) { queue.Enqueue(child); var comp = child.GetComponent(); if (comp != null) yield return comp; } } } public static void CreateIfNull(ref T obj) where T : class, new() { if (obj == null) obj = new T(); } #endregion #region Maths public static float Exp2(float x) { return Mathf.Exp(x * 0.69314718055994530941723212145818f); } // Adapted heavily from PlayDead's TAA code // https://github.com/playdeadgames/temporal/blob/master/Assets/Scripts/Extensions.cs public static Matrix4x4 GetJitteredPerspectiveProjectionMatrix(Camera camera, Vector2 offset) { float vertical = Mathf.Tan(0.5f * Mathf.Deg2Rad * camera.fieldOfView); float horizontal = vertical * camera.aspect; float near = camera.nearClipPlane; float far = camera.farClipPlane; offset.x *= horizontal / (0.5f * camera.pixelWidth); offset.y *= vertical / (0.5f * camera.pixelHeight); float left = (offset.x - horizontal) * near; float right = (offset.x + horizontal) * near; float top = (offset.y + vertical) * near; float bottom = (offset.y - vertical) * near; var matrix = new Matrix4x4(); matrix[0, 0] = (2f * near) / (right - left); matrix[0, 1] = 0f; matrix[0, 2] = (right + left) / (right - left); matrix[0, 3] = 0f; matrix[1, 0] = 0f; matrix[1, 1] = (2f * near) / (top - bottom); matrix[1, 2] = (top + bottom) / (top - bottom); matrix[1, 3] = 0f; matrix[2, 0] = 0f; matrix[2, 1] = 0f; matrix[2, 2] = -(far + near) / (far - near); matrix[2, 3] = -(2f * far * near) / (far - near); matrix[3, 0] = 0f; matrix[3, 1] = 0f; matrix[3, 2] = -1f; matrix[3, 3] = 0f; return matrix; } public static Matrix4x4 GetJitteredOrthographicProjectionMatrix(Camera camera, Vector2 offset) { float vertical = camera.orthographicSize; float horizontal = vertical * camera.aspect; offset.x *= horizontal / (0.5f * camera.pixelWidth); offset.y *= vertical / (0.5f * camera.pixelHeight); float left = offset.x - horizontal; float right = offset.x + horizontal; float top = offset.y + vertical; float bottom = offset.y - vertical; return Matrix4x4.Ortho(left, right, bottom, top, camera.nearClipPlane, camera.farClipPlane); } public static Matrix4x4 GenerateJitteredProjectionMatrixFromOriginal(PostProcessRenderContext context, Matrix4x4 origProj, Vector2 jitter) { #if UNITY_2017_2_OR_NEWER var planes = origProj.decomposeProjection; float vertFov = Math.Abs(planes.top) + Math.Abs(planes.bottom); float horizFov = Math.Abs(planes.left) + Math.Abs(planes.right); var planeJitter = new Vector2(jitter.x * horizFov / context.screenWidth, jitter.y * vertFov / context.screenHeight); planes.left += planeJitter.x; planes.right += planeJitter.x; planes.top += planeJitter.y; planes.bottom += planeJitter.y; var jitteredMatrix = Matrix4x4.Frustum(planes); return jitteredMatrix; #else var rTan = (1.0f + origProj[0, 2]) / origProj[0, 0]; var lTan = (-1.0f + origProj[0, 2]) / origProj[0, 0]; var tTan = (1.0f + origProj[1, 2]) / origProj[1, 1]; var bTan = (-1.0f + origProj[1, 2]) / origProj[1, 1]; float tanVertFov = Math.Abs(tTan) + Math.Abs(bTan); float tanHorizFov = Math.Abs(lTan) + Math.Abs(rTan); jitter.x *= tanHorizFov / context.screenWidth; jitter.y *= tanVertFov / context.screenHeight; float left = jitter.x + lTan; float right = jitter.x + rTan; float top = jitter.y + tTan; float bottom = jitter.y + bTan; var jitteredMatrix = new Matrix4x4(); jitteredMatrix[0, 0] = 2f / (right - left); jitteredMatrix[0, 1] = 0f; jitteredMatrix[0, 2] = (right + left) / (right - left); jitteredMatrix[0, 3] = 0f; jitteredMatrix[1, 0] = 0f; jitteredMatrix[1, 1] = 2f / (top - bottom); jitteredMatrix[1, 2] = (top + bottom) / (top - bottom); jitteredMatrix[1, 3] = 0f; jitteredMatrix[2, 0] = 0f; jitteredMatrix[2, 1] = 0f; jitteredMatrix[2, 2] = origProj[2, 2]; jitteredMatrix[2, 3] = origProj[2, 3]; jitteredMatrix[3, 0] = 0f; jitteredMatrix[3, 1] = 0f; jitteredMatrix[3, 2] = -1f; jitteredMatrix[3, 3] = 0f; return jitteredMatrix; #endif } #endregion #region Reflection public static IEnumerable GetAllAssemblyTypes() { return AppDomain.CurrentDomain.GetAssemblies() .SelectMany(t => { // Ugly hack to handle mis-versioned dlls var innerTypes = new Type[0]; try { innerTypes = t.GetTypes(); } catch {} return innerTypes; }); } // Quick extension method to get the first attribute of type T on a given Type public static T GetAttribute(this Type type) where T : Attribute { Assert.IsTrue(type.IsDefined(typeof(T), false), "Attribute not found"); return (T)type.GetCustomAttributes(typeof(T), false)[0]; } // Returns all attributes set on a specific member // Note: doesn't include inherited attributes, only explicit ones public static Attribute[] GetMemberAttributes(Expression> expr) { Expression body = expr; if (body is LambdaExpression) body = ((LambdaExpression)body).Body; switch (body.NodeType) { case ExpressionType.MemberAccess: var fi = (FieldInfo)((MemberExpression)body).Member; return fi.GetCustomAttributes(false).Cast().ToArray(); default: throw new InvalidOperationException(); } } // Returns a string path from an expression - mostly used to retrieve serialized properties // without hardcoding the field path. Safer, and allows for proper refactoring. public static string GetFieldPath(Expression> expr) { MemberExpression me; switch (expr.Body.NodeType) { case ExpressionType.MemberAccess: me = expr.Body as MemberExpression; break; default: throw new InvalidOperationException(); } var members = new List(); while (me != null) { members.Add(me.Member.Name); me = me.Expression as MemberExpression; } var sb = new StringBuilder(); for (int i = members.Count - 1; i >= 0; i--) { sb.Append(members[i]); if (i > 0) sb.Append('.'); } return sb.ToString(); } public static object GetParentObject(string path, object obj) { var fields = path.Split('.'); if (fields.Length == 1) return obj; var info = obj.GetType().GetField(fields[0], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); obj = info.GetValue(obj); return GetParentObject(string.Join(".", fields, 1, fields.Length - 1), obj); } #endregion } }