using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using UnityEngine.Assertions; #if UNITY_EDITOR using UnityEditor; #endif namespace UnityEngine.Rendering.PostProcessing { using SceneManagement; using UnityObject = UnityEngine.Object; using LoadAction = RenderBufferLoadAction; using StoreAction = RenderBufferStoreAction; /// <summary> /// A set of runtime utilities used by the post-processing stack. /// </summary> public static class RuntimeUtilities { #region Textures static Texture2D m_WhiteTexture; /// <summary> /// A 1x1 white texture. /// </summary> /// <remarks> /// This texture is only created once and recycled afterward. You shouldn't modify it. /// </remarks> public static Texture2D whiteTexture { get { if (m_WhiteTexture == null) { m_WhiteTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false) { name = "White Texture" }; m_WhiteTexture.SetPixel(0, 0, Color.white); m_WhiteTexture.Apply(); } return m_WhiteTexture; } } static Texture3D m_WhiteTexture3D; /// <summary> /// A 1x1x1 white texture. /// </summary> /// <remarks> /// This texture is only created once and recycled afterward. You shouldn't modify it. /// </remarks> public static Texture3D whiteTexture3D { get { if (m_WhiteTexture3D == null) { m_WhiteTexture3D = new Texture3D(1, 1, 1, TextureFormat.ARGB32, false) { name = "White Texture 3D" }; m_WhiteTexture3D.SetPixels(new Color[] { Color.white }); m_WhiteTexture3D.Apply(); } return m_WhiteTexture3D; } } static Texture2D m_BlackTexture; /// <summary> /// A 1x1 black texture. /// </summary> /// <remarks> /// This texture is only created once and recycled afterward. You shouldn't modify it. /// </remarks> public static Texture2D blackTexture { get { if (m_BlackTexture == null) { m_BlackTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false) { name = "Black Texture" }; m_BlackTexture.SetPixel(0, 0, Color.black); m_BlackTexture.Apply(); } return m_BlackTexture; } } static Texture3D m_BlackTexture3D; /// <summary> /// A 1x1x1 black texture. /// </summary> /// <remarks> /// This texture is only created once and recycled afterward. You shouldn't modify it. /// </remarks> public static Texture3D blackTexture3D { get { if (m_BlackTexture3D == null) { m_BlackTexture3D = new Texture3D(1, 1, 1, TextureFormat.ARGB32, false) { name = "Black Texture 3D" }; m_BlackTexture3D.SetPixels(new Color[] { Color.black }); m_BlackTexture3D.Apply(); } return m_BlackTexture3D; } } static Texture2D m_TransparentTexture; /// <summary> /// A 1x1 transparent texture. /// </summary> /// <remarks> /// This texture is only created once and recycled afterward. You shouldn't modify it. /// </remarks> public static Texture2D transparentTexture { get { if (m_TransparentTexture == null) { m_TransparentTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false) { name = "Transparent Texture" }; m_TransparentTexture.SetPixel(0, 0, Color.clear); m_TransparentTexture.Apply(); } return m_TransparentTexture; } } static Texture3D m_TransparentTexture3D; /// <summary> /// A 1x1x1 transparent texture. /// </summary> /// <remarks> /// This texture is only created once and recycled afterward. You shouldn't modify it. /// </remarks> public static Texture3D transparentTexture3D { get { if (m_TransparentTexture3D == null) { m_TransparentTexture3D = new Texture3D(1, 1, 1, TextureFormat.ARGB32, false) { name = "Transparent Texture 3D" }; m_TransparentTexture3D.SetPixels(new Color[] { Color.clear }); m_TransparentTexture3D.Apply(); } return m_TransparentTexture3D; } } static Dictionary<int, Texture2D> m_LutStrips = new Dictionary<int, Texture2D>(); /// <summary> /// Gets a 2D lookup table for color grading use. Its size will be <c>width = height * height</c>. /// </summary> /// <param name="size">The height of the lookup table</param> /// <returns>A 2D lookup table</returns> /// <remarks> /// Lookup tables are recycled and only created once per size. You shouldn't modify them. /// </remarks> public static Texture2D GetLutStrip(int size) { Texture2D texture; if (!m_LutStrips.TryGetValue(size, out texture)) { int width = size * size; int height = size; var pixels = new Color[width * height]; float inv = 1f / (size - 1f); for (int z = 0; z < size; z++) { var offset = z * size; var b = z * inv; for (int y = 0; y < size; y++) { var g = y * inv; for (int x = 0; x < size; x++) { var r = x * inv; pixels[y * width + offset + x] = new Color(r, g, b); } } } var format = TextureFormat.RGBAHalf; if (!format.IsSupported()) format = TextureFormat.ARGB32; texture = new Texture2D(size * size, size, format, false, true) { name = "Strip Lut" + size, hideFlags = HideFlags.DontSave, filterMode = FilterMode.Bilinear, wrapMode = TextureWrapMode.Clamp, anisoLevel = 0 }; texture.SetPixels(pixels); texture.Apply(); m_LutStrips.Add(size, texture); } return texture; } #endregion #region Rendering static PostProcessResources s_Resources; static Mesh s_FullscreenTriangle; /// <summary> /// A fullscreen triangle mesh. /// </summary> 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<Vector3> { 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; /// <summary> /// A simple copy material to use with the builtin pipelines. /// </summary> public static Material copyStdMaterial { get { if (s_CopyStdMaterial != null) return s_CopyStdMaterial; Assert.IsNotNull(s_Resources); var shader = s_Resources.shaders.copyStd; s_CopyStdMaterial = new Material(shader) { name = "PostProcess - CopyStd", hideFlags = HideFlags.HideAndDontSave }; return s_CopyStdMaterial; } } static Material s_CopyStdFromDoubleWideMaterial; /// <summary> /// A double-wide copy material to use with VR and the builtin pipelines. /// </summary> public static Material copyStdFromDoubleWideMaterial { get { if (s_CopyStdFromDoubleWideMaterial != null) return s_CopyStdFromDoubleWideMaterial; Assert.IsNotNull(s_Resources); var shader = s_Resources.shaders.copyStdFromDoubleWide; s_CopyStdFromDoubleWideMaterial = new Material(shader) { name = "PostProcess - CopyStdFromDoubleWide", hideFlags = HideFlags.HideAndDontSave }; return s_CopyStdFromDoubleWideMaterial; } } static Material s_CopyMaterial; /// <summary> /// A simple copy material independent from the rendering pipeline. /// </summary> public static Material copyMaterial { get { if (s_CopyMaterial != null) return s_CopyMaterial; Assert.IsNotNull(s_Resources); var shader = s_Resources.shaders.copy; s_CopyMaterial = new Material(shader) { name = "PostProcess - Copy", hideFlags = HideFlags.HideAndDontSave }; return s_CopyMaterial; } } static Material s_CopyFromTexArrayMaterial; /// <summary> /// A copy material with a texture array slice as a source for the builtin pipelines. /// </summary> public static Material copyFromTexArrayMaterial { get { if (s_CopyFromTexArrayMaterial != null) return s_CopyFromTexArrayMaterial; Assert.IsNotNull(s_Resources); var shader = s_Resources.shaders.copyStdFromTexArray; s_CopyFromTexArrayMaterial = new Material(shader) { name = "PostProcess - CopyFromTexArray", hideFlags = HideFlags.HideAndDontSave }; return s_CopyFromTexArrayMaterial; } } static PropertySheet s_CopySheet; /// <summary> /// A pre-configured <see cref="PropertySheet"/> for <see cref="copyMaterial"/>. /// </summary> public static PropertySheet copySheet { get { if (s_CopySheet == null) s_CopySheet = new PropertySheet(copyMaterial); return s_CopySheet; } } static PropertySheet s_CopyFromTexArraySheet; /// <summary> /// A pre-configured <see cref="PropertySheet"/> for <see cref="copyFromTexArrayMaterial"/>. /// </summary> public static PropertySheet copyFromTexArraySheet { get { if (s_CopyFromTexArraySheet == null) s_CopyFromTexArraySheet = new PropertySheet(copyFromTexArrayMaterial); return s_CopyFromTexArraySheet; } } internal static bool isValidResources() { return s_Resources != null; } internal static void UpdateResources(PostProcessResources resources) { Destroy(s_CopyMaterial); Destroy(s_CopyStdMaterial); Destroy(s_CopyFromTexArrayMaterial); Destroy(s_CopyStdFromDoubleWideMaterial); s_CopyMaterial = null; s_CopyStdMaterial = null; s_CopyFromTexArrayMaterial = null; s_CopyStdFromDoubleWideMaterial = null; s_CopySheet = null; s_CopyFromTexArraySheet = null; s_Resources = resources; } /// <summary> /// Sets the current render target using specified <see cref="RenderBufferLoadAction"/>. /// </summary> /// <param name="cmd">The command buffer to set the render target on</param> /// <param name="rt">The render target to set</param> /// <param name="loadAction">The load action</param> /// <param name="storeAction">The store action</param> /// <remarks> /// <see cref="RenderBufferLoadAction"/> are only used on Unity 2018.2 or newer. /// </remarks> public static void SetRenderTargetWithLoadStoreAction(this CommandBuffer cmd, RenderTargetIdentifier rt, RenderBufferLoadAction loadAction, RenderBufferStoreAction storeAction) { #if UNITY_2018_2_OR_NEWER cmd.SetRenderTarget(rt, loadAction, storeAction); #else cmd.SetRenderTarget(rt); #endif } /// <summary> /// Sets the current render target using specified <see cref="RenderBufferLoadAction"/>. /// </summary> /// <param name="cmd">The command buffer to set the render target on</param> /// <param name="rt">The render target to set</param> /// <param name="loadAction">The load action</param> /// <param name="storeAction">The store action</param> /// <param name="depthLoadAction">The load action for the depth/stencil part of rt</param> /// <param name="depthStoreAction">The store action for the depth/stencil part of rt</param> /// <remarks> /// <see cref="RenderBufferLoadAction"/> are only used on Unity 2018.2 or newer. /// </remarks> public static void SetRenderTargetWithLoadStoreAction(this CommandBuffer cmd, RenderTargetIdentifier rt, RenderBufferLoadAction loadAction, RenderBufferStoreAction storeAction, RenderBufferLoadAction depthLoadAction, RenderBufferStoreAction depthStoreAction) { #if UNITY_2018_2_OR_NEWER cmd.SetRenderTarget(rt, loadAction, storeAction, depthLoadAction, depthStoreAction); #else cmd.SetRenderTarget(rt); #endif } /// <summary> /// Sets the current render target and its depth using specified <see cref="RenderBufferLoadAction"/>. /// </summary> /// <param name="cmd">The command buffer to set the render target on</param> /// <param name="color">The render target to set as color</param> /// <param name="colorLoadAction">The load action for the color render target</param> /// <param name="colorStoreAction">The store action for the color render target</param> /// <param name="depth">The render target to set as depth</param> /// <param name="depthLoadAction">The load action for the depth render target</param> /// <param name="depthStoreAction">The store action for the depth render target</param> public static void SetRenderTargetWithLoadStoreAction(this CommandBuffer cmd, RenderTargetIdentifier color, RenderBufferLoadAction colorLoadAction, RenderBufferStoreAction colorStoreAction, RenderTargetIdentifier depth, RenderBufferLoadAction depthLoadAction, RenderBufferStoreAction depthStoreAction) { #if UNITY_2018_2_OR_NEWER cmd.SetRenderTarget(color, colorLoadAction, colorStoreAction, depth, depthLoadAction, depthStoreAction); #else cmd.SetRenderTarget(color, depth); #endif } /// <summary> /// Does a copy of source to destination using a fullscreen triangle. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <param name="clear">Should the destination target be cleared?</param> /// <param name="viewport">An optional viewport to consider for the blit</param> /// <param name="preserveDepth">Should the depth buffer be preserved?</param> public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, bool clear = false, Rect? viewport = null, bool preserveDepth = false) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); var colorLoad = viewport == null ? LoadAction.DontCare : LoadAction.Load; cmd.SetRenderTargetWithLoadStoreAction(destination, colorLoad, StoreAction.Store, preserveDepth ? LoadAction.Load : colorLoad, StoreAction.Store); if (viewport != null) cmd.SetViewport(viewport.Value); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, copyMaterial, 0, 0); } /// <summary> /// Blits a fullscreen triangle using a given material. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <param name="propertySheet">The property sheet to use</param> /// <param name="pass">The pass from the material to use</param> /// <param name="loadAction">The load action for this blit</param> /// <param name="viewport">An optional viewport to consider for the blit</param> /// <param name="preserveDepth">Should the depth buffer be preserved?</param> public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, PropertySheet propertySheet, int pass, RenderBufferLoadAction loadAction, Rect? viewport = null, bool preserveDepth = false) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); #if UNITY_2018_2_OR_NEWER bool clear = (loadAction == LoadAction.Clear); if (clear) loadAction = LoadAction.DontCare; #else bool clear = false; #endif if (viewport != null) loadAction = LoadAction.Load; cmd.SetRenderTargetWithLoadStoreAction(destination, loadAction, StoreAction.Store, preserveDepth ? LoadAction.Load : loadAction, StoreAction.Store); if (viewport != null) cmd.SetViewport(viewport.Value); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); } /// <summary> /// Blits a fullscreen triangle using a given material. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <param name="propertySheet">The property sheet to use</param> /// <param name="pass">The pass from the material to use</param> /// <param name="clear">Should the destination target be cleared?</param> /// <param name="viewport">An optional viewport to consider for the blit</param> /// <param name="preserveDepth">Should the depth buffer be preserved?</param> public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, PropertySheet propertySheet, int pass, bool clear = false, Rect? viewport = null, bool preserveDepth = false) { #if UNITY_2018_2_OR_NEWER cmd.BlitFullscreenTriangle(source, destination, propertySheet, pass, clear ? LoadAction.Clear : LoadAction.DontCare, viewport, preserveDepth); #else cmd.SetGlobalTexture(ShaderIDs.MainTex, source); var loadAction = viewport == null ? LoadAction.DontCare : LoadAction.Load; cmd.SetRenderTargetWithLoadStoreAction(destination, loadAction, StoreAction.Store, preserveDepth ? LoadAction.Load : loadAction, StoreAction.Store); if (viewport != null) cmd.SetViewport(viewport.Value); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); #endif } /// <summary> /// Blits a fullscreen triangle from a double-wide source. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <param name="material">The material to use for the blit</param> /// <param name="pass">The pass from the material to use</param> /// <param name="eye">The target eye</param> public static void BlitFullscreenTriangleFromDoubleWide(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Material material, int pass, int eye) { Vector4 uvScaleOffset = new Vector4(0.5f, 1.0f, 0, 0); if (eye == 1) uvScaleOffset.z = 0.5f; cmd.SetGlobalVector(ShaderIDs.UVScaleOffset, uvScaleOffset); cmd.BuiltinBlit(source, destination, material, pass); } /// <summary> /// Blits a fullscreen triangle to a double-wide destination. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <param name="propertySheet">The property sheet to use</param> /// <param name="pass">The pass from the material to use</param> /// <param name="eye">The target eye</param> public static void BlitFullscreenTriangleToDoubleWide(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, PropertySheet propertySheet, int pass, int eye) { Vector4 posScaleOffset = new Vector4(0.5f, 1.0f, -0.5f, 0); if (eye == 1) posScaleOffset.z = 0.5f; propertySheet.EnableKeyword("STEREO_DOUBLEWIDE_TARGET"); propertySheet.properties.SetVector(ShaderIDs.PosScaleOffset, posScaleOffset); cmd.BlitFullscreenTriangle(source, destination, propertySheet, 0); } /// <summary> /// Blits a fullscreen triangle using a given material. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source texture array</param> /// <param name="destination">The destination render target</param> /// <param name="propertySheet">The property sheet to use</param> /// <param name="pass">The pass from the material to use</param> /// <param name="clear">Should the destination target be cleared?</param> /// <param name="depthSlice">The slice to use for the texture array</param> public static void BlitFullscreenTriangleFromTexArray(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, PropertySheet propertySheet, int pass, bool clear = false, int depthSlice = -1) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); cmd.SetGlobalFloat(ShaderIDs.DepthSlice, depthSlice); cmd.SetRenderTargetWithLoadStoreAction(destination, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); } /// <summary> /// Blits a fullscreen triangle using a given material. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <param name="propertySheet">The property sheet to use</param> /// <param name="pass">The pass from the material to use</param> /// <param name="clear">Should the destination target be cleared?</param> /// <param name="depthSlice">The array slice to consider as a source</param> public static void BlitFullscreenTriangleToTexArray(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, PropertySheet propertySheet, int pass, bool clear = false, int depthSlice = -1) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); cmd.SetGlobalFloat(ShaderIDs.DepthSlice, depthSlice); cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); } /// <summary> /// Blits a fullscreen triangle using a given material. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <param name="depth">The depth render target</param> /// <param name="propertySheet">The property sheet to use</param> /// <param name="pass">The pass from the material to use</param> /// <param name="clear">Should the destination target be cleared?</param> /// <param name="viewport">An optional viewport to consider for the blit</param> public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, RenderTargetIdentifier depth, PropertySheet propertySheet, int pass, bool clear = false, Rect? viewport = null) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); LoadAction loadAction = viewport == null ? LoadAction.DontCare : LoadAction.Load; if (clear) { cmd.SetRenderTargetWithLoadStoreAction(destination, loadAction, StoreAction.Store, depth, loadAction, StoreAction.Store); cmd.ClearRenderTarget(true, true, Color.clear); } else { cmd.SetRenderTargetWithLoadStoreAction(destination, loadAction, StoreAction.Store, depth, LoadAction.Load, StoreAction.Store); } if (viewport != null) cmd.SetViewport(viewport.Value); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); } /// <summary> /// Blits a fullscreen triangle using a given material. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destinations">An array of destinations render targets</param> /// <param name="depth">The depth render target</param> /// <param name="propertySheet">The property sheet to use</param> /// <param name="pass">The pass from the material to use</param> /// <param name="clear">Should the destination target be cleared?</param> /// <param name="viewport">An optional viewport to consider for the blit</param> public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier[] destinations, RenderTargetIdentifier depth, PropertySheet propertySheet, int pass, bool clear = false, Rect? viewport = null) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); cmd.SetRenderTarget(destinations, depth); if (viewport != null) cmd.SetViewport(viewport.Value); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); } /// <summary> /// Does a copy of source to destination using the builtin blit command. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> public static void BuiltinBlit(this CommandBuffer cmd, Rendering.RenderTargetIdentifier source, RenderTargetIdentifier destination) { #if UNITY_2018_2_OR_NEWER cmd.SetRenderTarget(destination, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); destination = BuiltinRenderTextureType.CurrentActive; #endif cmd.Blit(source, destination); } /// <summary> /// Blits a fullscreen quad using the builtin blit command and a given material. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <param name="mat">The material to use for the blit</param> /// <param name="pass">The pass from the material to use</param> public static void BuiltinBlit(this CommandBuffer cmd, Rendering.RenderTargetIdentifier source, RenderTargetIdentifier destination, Material mat, int pass = 0) { #if UNITY_2018_2_OR_NEWER cmd.SetRenderTarget(destination, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); destination = BuiltinRenderTextureType.CurrentActive; #endif cmd.Blit(source, destination, mat, pass); } // Fast basic copy texture if available, falls back to blit copy if not // Assumes that both textures have the exact same type and format /// <summary> /// Copies the content of a texture into the other. Both textures must have the same size /// and format or this method will fail. /// </summary> /// <param name="cmd">The command buffer to use</param> /// <param name="source">The source render target</param> /// <param name="destination">The destination render target</param> /// <remarks> /// If the CopyTexture command isn't supported on the target platform it will revert to a /// fullscreen blit command instead. /// </remarks> 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 /// <summary> /// Returns <c>true</c> if a scriptable render pipeline is currently in use, <c>false</c> /// otherwise. /// </summary> public static bool scriptableRenderPipelineActive { #if UNITY_2019_3_OR_NEWER get { return GraphicsSettings.currentRenderPipeline != null; } #else get { return GraphicsSettings.renderPipelineAsset != null; } #endif } /// <summary> /// Returns <c>true</c> if deferred shading is supported on the target platform, /// <c>false</c> otherwise. /// </summary> public static bool supportsDeferredShading { get { return scriptableRenderPipelineActive || GraphicsSettings.GetShaderMode(BuiltinShaderType.DeferredShading) != BuiltinShaderMode.Disabled; } } /// <summary> /// Returns <c>true</c> if <see cref="DepthTextureMode.DepthNormals"/> is supported on the /// target platform, <c>false</c> otherwise. /// </summary> public static bool supportsDepthNormals { get { return scriptableRenderPipelineActive || GraphicsSettings.GetShaderMode(BuiltinShaderType.DepthNormals) != BuiltinShaderMode.Disabled; } } #if UNITY_EDITOR /// <summary> /// Returns <c>true</c> if single-pass stereo rendering is selected, <c>false</c> otherwise. /// </summary> /// <remarks> /// This property only works in the editor. /// </remarks> public static bool isSinglePassStereoSelected { get { #if (ENABLE_VR_MODULE && ENABLE_VR) && !UNITY_2020_1_OR_NEWER return UnityEditorInternal.VR.VREditor.GetVREnabledOnTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)) && PlayerSettings.stereoRenderingPath == UnityEditor.StereoRenderingPath.SinglePass; #else return false; #endif } } #endif /// <summary> /// Returns <c>true</c> if single-pass stereo rendering is active, <c>false</c> otherwise. /// </summary> /// <remarks> /// This property only works in the editor. /// </remarks> // TODO: Check for SPSR support at runtime public static bool isSinglePassStereoEnabled { get { #if UNITY_EDITOR return isSinglePassStereoSelected && Application.isPlaying; #elif !(ENABLE_VR_MODULE && ENABLE_VR) return false; #else return UnityEngine.XR.XRSettings.eyeTextureDesc.vrUsage == VRTextureUsage.TwoEyes; #endif } } /// <summary> /// Returns <c>true</c> if VR is enabled, <c>false</c> otherwise. /// </summary> public static bool isVREnabled { get { #if (ENABLE_VR_MODULE && ENABLE_VR) && UNITY_EDITOR && !UNITY_2020_1_OR_NEWER return UnityEditorInternal.VR.VREditor.GetVREnabledOnTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)); #elif UNITY_XBOXONE || !(ENABLE_VR_MODULE && ENABLE_VR) return false; #else return UnityEngine.XR.XRSettings.enabled; #endif } } /// <summary> /// Returns <c>true</c> if the target platform is Android and the selected API is OpenGL, /// <c>false</c> otherwise. /// </summary> public static bool isAndroidOpenGL { get { return Application.platform == RuntimePlatform.Android && SystemInfo.graphicsDeviceType != GraphicsDeviceType.Vulkan; } } /// <summary> /// Gets the default HDR render texture format for the current target platform. /// </summary> public static RenderTextureFormat defaultHDRRenderTextureFormat { get { #if !UNITY_2019_3_OR_NEWER && (UNITY_ANDROID || UNITY_IPHONE || UNITY_TVOS || UNITY_EDITOR) RenderTextureFormat format = RenderTextureFormat.RGB111110Float; #if UNITY_EDITOR var target = EditorUserBuildSettings.activeBuildTarget; if (target != BuildTarget.Android && target != BuildTarget.iOS && target != BuildTarget.tvOS) return RenderTextureFormat.DefaultHDR; #endif // UNITY_EDITOR if (format.IsSupported()) return format; #endif // #if !UNITY_2019_3_OR_NEWER && (UNITY_ANDROID || UNITY_IPHONE || UNITY_TVOS || UNITY_EDITOR) return RenderTextureFormat.DefaultHDR; } } /// <summary> /// Checks if a given render texture format is a floating-point format. /// </summary> /// <param name="format">The format to test</param> /// <returns><c>true</c> if the format is floating-point, <c>false</c> otherwise</returns> public static bool isFloatingPointFormat(RenderTextureFormat format) { return format == RenderTextureFormat.DefaultHDR || format == RenderTextureFormat.ARGBHalf || format == RenderTextureFormat.ARGBFloat || format == RenderTextureFormat.RGFloat || format == RenderTextureFormat.RGHalf || format == RenderTextureFormat.RFloat || format == RenderTextureFormat.RHalf || format == RenderTextureFormat.RGB111110Float; } /// <summary> /// Checks if a given render texture format has an alpha channel. /// </summary> /// <param name="format">The format to test</param> /// <returns><c>true</c> if the format has an alpha channel, <c>false</c> otherwise</returns> internal static bool hasAlpha(RenderTextureFormat format) { UnityEngine.Experimental.Rendering.GraphicsFormat gformat = UnityEngine.Experimental.Rendering.GraphicsFormatUtility.GetGraphicsFormat(format, RenderTextureReadWrite.Default); return UnityEngine.Experimental.Rendering.GraphicsFormatUtility.HasAlphaChannel(gformat); } /// <summary> /// Properly destroys a given Unity object. /// </summary> /// <param name="obj">The object to destroy</param> 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 } } /// <summary> /// Returns <c>true</c> if the current color space setting is set to <c>Linear</c>, /// <c>false</c> otherwise. /// </summary> public static bool isLinearColorSpace { get { return QualitySettings.activeColorSpace == ColorSpace.Linear; } } /// <summary> /// Checks if resolved depth is available on the current target platform. /// </summary> /// <param name="camera">A rendering camera</param> /// <returns><c>true</c> if resolved depth is available, <c>false</c> otherwise</returns> 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 #if UNITY_GAMECORE || gtype == GraphicsDeviceType.GameCoreXboxSeries || gtype == GraphicsDeviceType.GameCoreXboxOne #endif || gtype == GraphicsDeviceType.XboxOne || gtype == GraphicsDeviceType.XboxOneD3D12 ); } /// <summary> /// Properly destroys a given profile. /// </summary> /// <param name="profile">The profile to destroy</param> /// <param name="destroyEffects">Should we destroy all the embedded settings?</param> public static void DestroyProfile(PostProcessProfile profile, bool destroyEffects) { if (destroyEffects) { foreach (var effect in profile.settings) Destroy(effect); } Destroy(profile); } /// <summary> /// Properly destroys a volume. /// </summary> /// <param name="volume">The volume to destroy</param> /// <param name="destroyProfile">Should we destroy the attached profile?</param> /// <param name="destroyGameObject">Should we destroy the volume Game Object?</param> public static void DestroyVolume(PostProcessVolume volume, bool destroyProfile, bool destroyGameObject = false) { if (destroyProfile) DestroyProfile(volume.profileRef, true); var gameObject = volume.gameObject; Destroy(volume); if (destroyGameObject) Destroy(gameObject); } /// <summary> /// Checks if a post-processing layer is active. /// </summary> /// <param name="layer">The layer to check; can be <c>null</c></param> /// <returns><c>true</c> if the layer is enabled, <c>false</c> otherwise</returns> public static bool IsPostProcessingActive(PostProcessLayer layer) { return layer != null && layer.enabled; } /// <summary> /// Checks if temporal anti-aliasing is active on a given post-process layer. /// </summary> /// <param name="layer">The layer to check</param> /// <returns><c>true</c> if temporal anti-aliasing is active, <c>false</c> otherwise</returns> public static bool IsTemporalAntialiasingActive(PostProcessLayer layer) { return IsPostProcessingActive(layer) && layer.antialiasingMode == PostProcessLayer.Antialiasing.TemporalAntialiasing && layer.temporalAntialiasing.IsSupported(); } /// <summary> /// Gets all scene objects in the hierarchy, including inactive objects. This method is slow /// on large scenes and should be used with extreme caution. /// </summary> /// <typeparam name="T">The component to look for</typeparam> /// <returns>A list of all components of type <c>T</c> in the scene</returns> public static IEnumerable<T> GetAllSceneObjects<T>() where T : Component { var queue = new Queue<Transform>(); var roots = SceneManager.GetActiveScene().GetRootGameObjects(); foreach (var root in roots) { queue.Enqueue(root.transform); var comp = root.GetComponent<T>(); if (comp != null) yield return comp; } while (queue.Count > 0) { foreach (Transform child in queue.Dequeue()) { queue.Enqueue(child); var comp = child.GetComponent<T>(); if (comp != null) yield return comp; } } } /// <summary> /// Creates an instance of a class if it's <c>null</c>. /// </summary> /// <typeparam name="T">The type to create</typeparam> /// <param name="obj">A reference to an instance to check and create if needed</param> public static void CreateIfNull<T>(ref T obj) where T : class, new() { if (obj == null) obj = new T(); } #endregion #region Maths /// <summary> /// Returns the base-2 exponential function of <paramref name="x"/>, which is <c>2</c> /// raised to the power <paramref name="x"/>. /// </summary> /// <param name="x">Value of the exponent</param> /// <returns>The base-2 exponential function of <paramref name="x"/></returns> public static float Exp2(float x) { return Mathf.Exp(x * 0.69314718055994530941723212145818f); } /// <summary> /// Gets a jittered perspective projection matrix for a given camera. /// </summary> /// <param name="camera">The camera to build the projection matrix for</param> /// <param name="offset">The jitter offset</param> /// <returns>A jittered projection matrix</returns> public static Matrix4x4 GetJitteredPerspectiveProjectionMatrix(Camera camera, Vector2 offset) { float near = camera.nearClipPlane; float far = camera.farClipPlane; float vertical = Mathf.Tan(0.5f * Mathf.Deg2Rad * camera.fieldOfView) * near; float horizontal = vertical * camera.aspect; offset.x *= horizontal / (0.5f * camera.pixelWidth); offset.y *= vertical / (0.5f * camera.pixelHeight); var matrix = camera.projectionMatrix; matrix[0, 2] += offset.x / horizontal; matrix[1, 2] += offset.y / vertical; return matrix; } /// <summary> /// Gets a jittered orthographic projection matrix for a given camera. /// </summary> /// <param name="camera">The camera to build the orthographic matrix for</param> /// <param name="offset">The jitter offset</param> /// <returns>A jittered projection matrix</returns> 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); } /// <summary> /// Gets a jittered perspective projection matrix from an original projection matrix. /// </summary> /// <param name="context">The current render context</param> /// <param name="origProj">The original projection matrix</param> /// <param name="jitter">The jitter offset</param> /// <returns>A jittered projection matrix</returns> public static Matrix4x4 GenerateJitteredProjectionMatrixFromOriginal(PostProcessRenderContext context, Matrix4x4 origProj, Vector2 jitter) { 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; } #endregion #region Reflection static IEnumerable<Type> m_AssemblyTypes; /// <summary> /// Gets all currently available assembly types. /// </summary> /// <returns>A list of all currently available assembly types</returns> /// <remarks> /// This method is slow and should be use with extreme caution. We recommend you use /// <see cref="GetAllTypesDerivedFrom{T}"/> instead if possible. /// </remarks> /// <seealso cref="GetAllTypesDerivedFrom{T}"/> public static IEnumerable<Type> GetAllAssemblyTypes() { if (m_AssemblyTypes == null) { m_AssemblyTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(t => { // Ugly hack to handle mis-versioned dlls var innerTypes = new Type[0]; try { innerTypes = t.GetTypes(); } catch { } return innerTypes; }); } return m_AssemblyTypes; } /// <summary> /// Gets all currently available assembly types derived from type <typeparamref name="T"/>. /// </summary> /// <typeparam name="T">The type to look for</typeparam> /// <returns>A list of all currently available assembly types derived from type <typeparamref name="T"/></returns> public static IEnumerable<Type> GetAllTypesDerivedFrom<T>() { #if UNITY_EDITOR && UNITY_2019_2_OR_NEWER return UnityEditor.TypeCache.GetTypesDerivedFrom<T>(); #else return GetAllAssemblyTypes().Where(t => t.IsSubclassOf(typeof(T))); #endif } /// <summary> /// Helper method to get the first attribute of type <c>T</c> on a given type. /// </summary> /// <typeparam name="T">The attribute type to look for</typeparam> /// <param name="type">The type to explore</param> /// <returns>The attribute found</returns> public static T GetAttribute<T>(this Type type) where T : Attribute { Assert.IsTrue(type.IsDefined(typeof(T), false), "Attribute not found"); return (T)type.GetCustomAttributes(typeof(T), false)[0]; } /// <summary> /// Returns all attributes set on a specific member. /// </summary> /// <typeparam name="TType">The class type where the member is defined</typeparam> /// <typeparam name="TValue">The member type</typeparam> /// <param name="expr">An expression path to the member</param> /// <returns>An array of attributes</returns> /// <remarks> /// This method doesn't return inherited attributes, only explicit ones. /// </remarks> public static Attribute[] GetMemberAttributes<TType, TValue>(Expression<Func<TType, TValue>> 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<Attribute>().ToArray(); default: throw new InvalidOperationException(); } } /// <summary> /// Returns a string path from an expression. This is mostly used to retrieve serialized /// properties without hardcoding the field path as a string and thus allowing proper /// refactoring features. /// </summary> /// <typeparam name="TType">The class type where the member is defined</typeparam> /// <typeparam name="TValue">The member type</typeparam> /// <param name="expr">An expression path fo the member</param> /// <returns>A string representation of the expression path</returns> public static string GetFieldPath<TType, TValue>(Expression<Func<TType, TValue>> expr) { MemberExpression me; switch (expr.Body.NodeType) { case ExpressionType.MemberAccess: me = expr.Body as MemberExpression; break; default: throw new InvalidOperationException(); } var members = new List<string>(); 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(); } #endregion } }