using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; using UnityEngine.Rendering.HighDefinition.Attributes; using UnityEngine.Rendering.HighDefinition.Compositor; using UnityEngine.Video; using UnityEditor; using UnityEditorInternal; namespace UnityEditor.Rendering.HighDefinition.Compositor { // Responsible for drawing the inspector UI of the composition manager [CustomEditor(typeof(CompositionManager))] internal class CompositionManagerEditor : Editor { static partial class Styles { static public readonly GUIContent k_CompositionGraph = EditorGUIUtility.TrTextContent("Composition Graph", "Specifies the Shader Graph that will be used to produce the final composited output."); static public readonly GUIContent k_OutputCamera = EditorGUIUtility.TrTextContent("Output Camera", "Specifies the camera that will output the final composited image."); static public readonly GUIContent k_EnablePreview = EditorGUIUtility.TrTextContent("Enable Preview", "When enabled, the compositor will generate the final composed frame even in edit mode."); static public readonly GUIContent k_InputFilters = EditorGUIUtility.TrTextContent("Input Filters", "A list of color filters that will be executed before composing the frame."); static public readonly GUIContent k_Properties = EditorGUIUtility.TrTextContent("Properties", "The properties of a layer or sub-layer."); static public readonly GUIContent k_RenderSchedule = EditorGUIUtility.TrTextContent("Render Schedule", "A list of layers and sub-layers in the scene. Layers are drawn from top to bottom."); static public readonly string k_AlphaWarningPipeline = "The rendering pipeline was not configured to output an alpha channel. You can select a color buffer format that supports alpha in the HDRP quality settings."; static public readonly string k_AlphaWarningPost = "The post processing system was not configured to process the alpha channel. You can select a buffer format that supports alpha in the HDRP quality settings."; static public readonly string k_ShaderWarning = "You must specify a composition graph to see an output from the compositor."; static public readonly GUIStyle k_HeaderStyle = new GUIStyle(EditorStyles.helpBox) { fontSize = CompositorStyle.k_HeaderFontSize }; } ReorderableList m_layerList; ReorderableList m_filterList; // Cached serialized properties SerializedCompositionManager m_SerializedProperties; List m_SerializedLayerProperties; List m_SerializedShaderProperties; bool m_IsEditorDirty = true; bool m_EnablePreview; bool layerListChange; CompositionManager m_compositionManager; public bool isDirty => m_IsEditorDirty; public int defaultSelection = -1; public int selectionIndex { get => m_layerList != null ? m_layerList.index : -1; set { if (m_layerList != null) m_layerList.index = Math.Min(value, m_layerList.count - 1); } } void AddLayerOfTypeCallback(object type) { Undo.RecordObject(m_compositionManager, "Add compositor sublayer"); m_compositionManager.AddNewLayer(m_layerList.index + 1, (CompositorLayer.LayerType)type); m_SerializedProperties.layerList.serializedObject.Update(); m_compositionManager.DeleteLayerRTs(); m_compositionManager.UpdateLayerSetup(); } void AddFilterOfTypeCallback(object type) { Undo.RecordObject(m_compositionManager, "Add input filter"); m_compositionManager.AddInputFilterAtLayer(CompositionFilter.Create((CompositionFilter.FilterType)type), m_layerList.index); m_SerializedProperties.layerList.serializedObject.Update(); CacheSerializedObjects(); } void DrawCompositionParameters() { ShaderPropertyUI.Draw(m_SerializedShaderProperties); } public bool CacheSerializedObjects() { try { m_SerializedProperties = new SerializedCompositionManager(serializedObject); } catch (Exception) { return false; } m_SerializedLayerProperties = new List(); m_SerializedShaderProperties = new List(); var serializedLayerList = m_SerializedProperties.layerList; for (int layerIndex = 0; layerIndex < serializedLayerList.arraySize; layerIndex++) { var serializedLayer = serializedLayerList.GetArrayElementAtIndex(layerIndex); m_SerializedLayerProperties.Add(new SerializedCompositionLayer(serializedLayer)); } var serializedPropertyList = m_SerializedProperties.shaderProperties; if (m_SerializedProperties.shaderProperties == null) { return false; } for (int pIndex = 0; pIndex < serializedPropertyList.arraySize; pIndex++) { var serializedProperty = serializedPropertyList.GetArrayElementAtIndex(pIndex); m_SerializedShaderProperties.Add(new SerializedShaderProperty(serializedProperty)); } return true; } void OnEnable() { CacheSerializedObjects(); m_IsEditorDirty = false; } public override void OnInspectorGUI() { m_compositionManager = (CompositionManager)target; if (m_compositionManager == null) { Debug.LogError("Compositor target was null"); return; } // Cache the serialized property fields if (m_IsEditorDirty || m_SerializedProperties == null) { if (CacheSerializedObjects()) { m_IsEditorDirty = false; } else { return; } } m_SerializedProperties.Update(); m_EnablePreview = EditorGUILayout.Toggle(Styles.k_EnablePreview, m_compositionManager.enableOutput); { m_compositionManager.enableOutput = m_EnablePreview; } bool cameraChange = false; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_SerializedProperties.outputCamera, Styles.k_OutputCamera); if (EditorGUI.EndChangeCheck()) { cameraChange = true; } EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_SerializedProperties.compositionShader, Styles.k_CompositionGraph); bool shaderChange = false; if (EditorGUI.EndChangeCheck()) { // Clear the existing shader (the new shader will be loaded in the next Update) m_IsEditorDirty = true; shaderChange = true; } if (m_compositionManager.shader == null) { EditorGUILayout.Space(5); EditorGUILayout.HelpBox(Styles.k_ShaderWarning, MessageType.Error); } EditorGUILayout.PropertyField(m_SerializedProperties.displayNumber); // Draw some warnings in case alpha is not fully supported if (m_compositionManager.alphaSupport == CompositionManager.AlphaChannelSupport.None) { EditorGUILayout.Space(5); EditorGUILayout.HelpBox(Styles.k_AlphaWarningPipeline, MessageType.Warning); } else if (m_compositionManager.alphaSupport == CompositionManager.AlphaChannelSupport.Rendering) { EditorGUILayout.Space(5); EditorGUILayout.HelpBox(Styles.k_AlphaWarningPost, MessageType.Warning); } // Now draw the composition shader properties DrawCompositionParameters(); // Now draw the list of layers EditorGUILayout.Separator(); layerListChange = false; if (m_layerList == null) { var serializedLayerList = m_SerializedProperties.layerList; m_layerList = new ReorderableList(m_SerializedProperties.compositorSO, serializedLayerList, true, false, true, true); // Pre-select the "default" item in the list (used to remember the last selected item when re-creating the Editor) if (defaultSelection >= 0) { m_layerList.index = Math.Min(defaultSelection, m_layerList.count - 1); } m_layerList.drawHeaderCallback = (Rect rect) => { }; m_layerList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { if (index < m_SerializedLayerProperties.Count) { var serializedProperties = m_SerializedLayerProperties[index]; CompositionLayerUI.DrawItemInList(rect, serializedProperties, m_compositionManager.GetRenderTarget(index), m_compositionManager.aspectRatio, m_compositionManager.alphaSupport != CompositionManager.AlphaChannelSupport.None); } }; m_layerList.onReorderCallbackWithDetails += (list, oldIndex, newIndex) => { layerListChange = true; m_IsEditorDirty = true; m_compositionManager.ReorderChildren(oldIndex, newIndex); if (!m_compositionManager.ValidateLayerListOrder(oldIndex, newIndex)) { // The new position is invalid, so set the currently selected layer to the old/starting position s m_layerList.index = oldIndex; } }; m_layerList.elementHeightCallback = (index) => { if (index < m_SerializedLayerProperties.Count) { return m_SerializedLayerProperties[index].GetListItemHeight(); } return 0; }; m_layerList.onAddCallback = (list) => { var menu = new GenericMenu(); menu.AddItem(new GUIContent("Image"), false, AddLayerOfTypeCallback, CompositorLayer.LayerType.Image); menu.AddItem(new GUIContent("Video"), false, AddLayerOfTypeCallback, CompositorLayer.LayerType.Video); menu.AddItem(new GUIContent("Camera"), false, AddLayerOfTypeCallback, CompositorLayer.LayerType.Camera); menu.ShowAsContext(); m_IsEditorDirty = true; m_SerializedProperties.layerList.serializedObject.Update(); EditorUtility.SetDirty(m_compositionManager.profile); }; m_layerList.onRemoveCallback = (list) => { Undo.RecordObject(m_compositionManager, "Remove compositor sublayer"); m_compositionManager.RemoveLayerAtIndex(list.index); m_IsEditorDirty = true; EditorUtility.SetDirty(m_compositionManager.profile); }; m_layerList.onSelectCallback = (index) => { m_filterList = null; }; m_layerList.onCanRemoveCallback = (list) => { return !m_compositionManager.IsOutputLayer(list.index); }; m_layerList.onCanAddCallback = (list) => { return list.index >= 0; }; m_layerList.headerHeight = 0; } EditorGUI.BeginChangeCheck(); EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField(Styles.k_RenderSchedule, Styles.k_HeaderStyle); m_layerList.DoLayoutList(); EditorGUILayout.EndVertical(); if (EditorGUI.EndChangeCheck()) { layerListChange = true; } float height = EditorGUIUtility.singleLineHeight; if (m_layerList.index >= 0) { height += m_SerializedLayerProperties[m_layerList.index].GetPropertiesHeight(); } var rectangle = EditorGUILayout.BeginVertical(GUILayout.Height(height)); EditorGUI.BeginChangeCheck(); if (m_layerList.index >= 0) { EditorGUILayout.LabelField(Styles.k_Properties, Styles.k_HeaderStyle); rectangle.y += EditorGUIUtility.singleLineHeight * 1.5f; rectangle.x += 5; rectangle.width -= 10; var serializedProperties = m_SerializedLayerProperties[m_layerList.index]; DrawLayerProperties(rectangle, serializedProperties, m_layerList.index, null); } EditorGUILayout.EndVertical(); if (EditorGUI.EndChangeCheck()) { // Also the layers might need to be re-initialized layerListChange = true; } if (m_SerializedProperties != null) { m_SerializedProperties.ApplyModifiedProperties(); } if (shaderChange) { // This needs to run after m_SerializedProperties.ApplyModifiedProperties CompositionUtils.LoadOrCreateCompositionProfileAsset(m_compositionManager); m_compositionManager.SetupCompositionMaterial(); CompositionUtils.SetDefaultLayers(m_compositionManager); } if (cameraChange) { m_compositionManager.DropCompositorCamera(); m_compositionManager.Init(); } if (layerListChange) { // Some properties were changed, mark the profile as dirty so it can be saved if the user saves the scene EditorUtility.SetDirty(m_compositionManager); EditorUtility.SetDirty(m_compositionManager.profile); m_compositionManager.DeleteLayerRTs(); m_compositionManager.UpdateLayerSetup(); } } void DrawLayerProperties(Rect rect, SerializedCompositionLayer serializedProperties, int layerIndex, RenderTexture preview = null) { if (serializedProperties == null) { return; } if (serializedProperties.outTarget.intValue != (int)CompositorLayer.OutputTarget.CameraStack) { CompositionLayerUI.DrawOutputLayerProperties(rect, serializedProperties, m_compositionManager.DeleteLayerRTs); } else { if (m_filterList == null) { m_filterList = new ReorderableList(serializedProperties.inputFilters.serializedObject, serializedProperties.inputFilters, true, true, true, true); m_filterList.onAddCallback = (list) => { var menu = new GenericMenu(); menu.AddItem(new GUIContent("Chroma Keying"), false, AddFilterOfTypeCallback, 0); menu.AddItem(new GUIContent("Alpha Mask"), false, AddFilterOfTypeCallback, 1); menu.ShowAsContext(); EditorUtility.SetDirty(m_compositionManager.profile); EditorUtility.SetDirty(m_compositionManager); }; m_filterList.drawElementCallback = (Rect r, int index, bool isActive, bool isFocused) => { if (index < m_SerializedLayerProperties[m_layerList.index].filterList.Count) { var serializedFilter = m_SerializedLayerProperties[m_layerList.index].filterList[index]; CompositionFilterUI.Draw(r, serializedFilter); } }; m_filterList.drawNoneElementCallback = (Rect r) => { using (new EditorGUI.DisabledScope(true)) { EditorGUI.LabelField(r, "List Is Empty", EditorStyles.label); } }; m_filterList.drawHeaderCallback = (Rect r) => { EditorGUI.LabelField(r, Styles.k_InputFilters, EditorStyles.largeLabel); }; m_filterList.elementHeightCallback = (index) => { if (index < m_SerializedLayerProperties[m_layerList.index].filterList.Count) { var filter = m_SerializedLayerProperties[m_layerList.index].filterList[index]; return filter.GetHeight(); } return CompositorStyle.k_Spacing; }; } CompositionLayerUI.DrawStackedLayerProperties(rect, serializedProperties, m_filterList); } } } }