using System; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.XR; namespace Golems { /// /// Creates a bunch of named RenderTexture for the camera that can then be used by other /// render passes. /// The textures can be used, for example, as a temporary texture when operating on the /// camera colour texture and then copying it back - saving the pass having to create /// its own texture. Another example is that the textures can be used to pass result between /// passes - since these temporary textures will be around from the time they are created to /// the end of the render pipeline. /// /// Other passes should access the textures via the static interface this class /// provides. /// /// NOTE: names are converted to integer hashes to save doing string operations every frame. /// public class TemporaryCameraCopyTextureRenderFeature : ScriptableRendererFeature { [SerializeField] private RenderPassEvent m_WhenToAdd = RenderPassEvent.BeforeRendering; [SerializeField] private List m_TextureNames; private TemporaryCameraTextureRenderPass m_Pass; private readonly Dictionary m_NameHashToTargetIdentifierMap = new Dictionary(); private readonly List m_NameHashesAndIds = new List(); public struct NameHashShaderIdPair { public int m_NameHash; public int m_ShaderId; } public override void Create() { if (s_HasInstance && s_Instance != null) { return; } s_HasInstance = true; s_Instance = this; // // Create the name hashes // m_NameHashesAndIds.Clear(); for (int i = 0; i < m_TextureNames.Count; i++) { m_NameHashesAndIds.Add( new NameHashShaderIdPair() { m_NameHash = GetNameHash(m_TextureNames[i]), m_ShaderId = Shader.PropertyToID(m_TextureNames[i]), }); } m_Pass = new TemporaryCameraTextureRenderPass(); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (m_Pass == null) { return; } #if ENABLE_VR var isXR = XRSettings.enabled; m_Pass.Setup(m_WhenToAdd, isXR, m_NameHashesAndIds, SetupTempTargetIdentifier, ClearTempTargetIdentifiers); #else m_Pass.Setup(m_WhenToAdd, false, m_NameHashesAndIds, SetupTempTargetIdentifier, ClearTempTargetIdentifiers); #endif renderer.EnqueuePass(m_Pass); } protected override void Dispose(bool disposing) { if (s_Instance == this) { s_HasInstance = false; s_Instance = null; } base.Dispose(disposing); } private bool InternalTryGetTempTextureIdentifier(int nameHash, out RenderTargetIdentifier outTargetIdentifier) { return m_NameHashToTargetIdentifierMap.TryGetValue(nameHash, out outTargetIdentifier); } private void InternalClearTempTargetIdentifiers() { m_NameHashToTargetIdentifierMap.Clear(); } private void InternalSetupTempTargetIdentifier(int nameHash, RenderTargetIdentifier identifier) { m_NameHashToTargetIdentifierMap[nameHash] = identifier; } // // // Static interface // // public const int k_InvalidNameHash = -1; private static bool s_HasInstance; private static TemporaryCameraCopyTextureRenderFeature s_Instance; /// /// Used by passes to ask for a named texture by its name hash. /// NOTE: hashes are used to avoid having a dictionary keyed by strings. /// /// /// /// true if the texture is available, false otherwise public static bool TryGetTempTextureIdentifier(int nameHash, out RenderTargetIdentifier outTargetIdentifier) { if (!s_HasInstance) { outTargetIdentifier = default; return false; } return s_Instance.InternalTryGetTempTextureIdentifier(nameHash, out outTargetIdentifier); } /// /// Given a name, this returns the hash of that name. /// public static int GetNameHash(string name) { if (string.IsNullOrEmpty(name)) { return k_InvalidNameHash; } return name.GetHashCode(); } private static void SetupTempTargetIdentifier(int nameHash, RenderTargetIdentifier identifier) { if (!s_HasInstance) { return; } s_Instance.InternalSetupTempTargetIdentifier(nameHash, identifier); } private static void ClearTempTargetIdentifiers() { if (!s_HasInstance) { return; } s_Instance.InternalClearTempTargetIdentifiers(); } #if UNITY_EDITOR private bool m_EditorNameHashToTargetIdentifierMapFoldout; private bool m_EditorNameHashesAndIdsFoldout; protected virtual void EditorOnInspectorGUIAfterBase() { EditorGUILayout.LabelField("Info", EditorStyles.boldLabel); var origIndent = EditorGUI.indentLevel; EditorGUI.indentLevel += 1; #if false // // NOTE: // m_NameHashToTargetIdentifierMap gets cleared every frame so no useful info // m_EditorNameHashToTargetIdentifierMapFoldout = EditorGUILayout.Foldout(m_EditorNameHashToTargetIdentifierMapFoldout, "Name hash to identifier map"); if (m_EditorNameHashToTargetIdentifierMapFoldout) { foreach (var nameHashIdentifier in m_NameHashToTargetIdentifierMap) { EditorGUILayout.LabelField( $"Name hash: {nameHashIdentifier.Key}, identifier {nameHashIdentifier.Value}"); } } #endif m_EditorNameHashesAndIdsFoldout = EditorGUILayout.Foldout(m_EditorNameHashesAndIdsFoldout, "Name hash to shader id map"); if (m_EditorNameHashesAndIdsFoldout) { foreach (var nameHashIds in m_NameHashesAndIds) { EditorGUILayout.LabelField( $"Name hash: {nameHashIds.m_NameHash}, identifier {nameHashIds.m_ShaderId}"); } } EditorGUI.BeginDisabledGroup(true); EditorGUILayout.ObjectField("Instance", s_Instance, typeof(TemporaryCameraCopyTextureRenderFeature), false); EditorGUI.EndDisabledGroup(); EditorGUI.indentLevel = origIndent; EditorGUILayout.LabelField("Controls", EditorStyles.boldLabel); } private static void EditorTextureNameAndHash(string label, string namedTemporaryName, int namedTemporaryNameHash) { EditorGUILayout.TextField(label); EditorGUI.indentLevel += 1; if (!string.IsNullOrEmpty(namedTemporaryName)) { EditorGUILayout.IntField(namedTemporaryName, namedTemporaryNameHash); } else { EditorGUILayout.IntField("null name", namedTemporaryNameHash); } EditorGUI.indentLevel -= 1; } [CustomEditor(typeof(TemporaryCameraCopyTextureRenderFeature))] private class TemporaryCameraCopyTextureRenderFeatureEditor : Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); var me = target as TemporaryCameraCopyTextureRenderFeature; if (me != null) { me.EditorOnInspectorGUIAfterBase(); } } } #endif } /// /// This is the pass that creates the named textures. /// public class TemporaryCameraTextureRenderPass : ScriptableRenderPass { private bool m_IsXR; private List m_NameHashesAndIds; private Action m_SetIdentifierAction; private Action m_ClearIdentifiersAction; public void Setup(RenderPassEvent whenToRender, bool isXR, List nameHashesAndIds, Action setIdentifierAction, Action clearIdentifiersAction) { renderPassEvent = whenToRender; m_IsXR = isXR; m_NameHashesAndIds = nameHashesAndIds; m_SetIdentifierAction = setIdentifierAction; m_ClearIdentifiersAction = clearIdentifiersAction; } public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { // // Create the temporary textures // #if UNITY_SWITCH var renderTextureDescriptor = renderingData.cameraData.cameraTargetDescriptor; var width = 1280; var height = 720; #elif ENABLE_VR var renderTextureDescriptor = m_IsXR ? XRSettings.eyeTextureDesc : renderingData.cameraData.cameraTargetDescriptor; #else var renderTextureDescriptor = renderingData.cameraData.cameraTargetDescriptor; #endif for (int i = 0; i < m_NameHashesAndIds.Count; i++) { #if UNITY_SWITCH cmd.GetTemporaryRT(m_NameHashesAndIds[i].m_ShaderId, width,height,renderTextureDescriptor.depthBufferBits,FilterMode.Bilinear,renderTextureDescriptor.colorFormat); #else cmd.GetTemporaryRT(m_NameHashesAndIds[i].m_ShaderId, renderTextureDescriptor); #endif m_SetIdentifierAction?.Invoke(m_NameHashesAndIds[i].m_NameHash, new RenderTargetIdentifier(m_NameHashesAndIds[i].m_ShaderId)); } } public override void OnCameraCleanup(CommandBuffer cmd) { m_ClearIdentifiersAction?.Invoke(); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { } } }