ScriptableRendererDataEditor.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Text.RegularExpressions;
  5. using UnityEngine;
  6. using UnityEngine.Rendering.Universal;
  7. using Object = UnityEngine.Object;
  8. namespace UnityEditor.Rendering.Universal
  9. {
  10. [CustomEditor(typeof(ScriptableRendererData), true)]
  11. public class ScriptableRendererDataEditor : Editor
  12. {
  13. class Styles
  14. {
  15. public static readonly GUIContent RenderFeatures =
  16. new GUIContent("Renderer Features",
  17. "A Renderer Feature is an asset that lets you add extra Render passes to a URP Renderer and configure their behavior.");
  18. public static readonly GUIContent PassNameField =
  19. new GUIContent("Name", "Render pass name. This name is the name displayed in Frame Debugger.");
  20. public static readonly GUIContent MissingFeature = new GUIContent("Missing RendererFeature",
  21. "Missing reference, due to compilation issues or missing files. you can attempt auto fix or choose to remove the feature.");
  22. public static GUIStyle BoldLabelSimple;
  23. static Styles()
  24. {
  25. BoldLabelSimple = new GUIStyle(EditorStyles.label);
  26. BoldLabelSimple.fontStyle = FontStyle.Bold;
  27. }
  28. }
  29. private SerializedProperty m_RendererFeatures;
  30. private SerializedProperty m_RendererFeaturesMap;
  31. private SerializedProperty m_FalseBool;
  32. [SerializeField] private bool falseBool = false;
  33. List<Editor> m_Editors = new List<Editor>();
  34. private void OnEnable()
  35. {
  36. m_RendererFeatures = serializedObject.FindProperty(nameof(ScriptableRendererData.m_RendererFeatures));
  37. m_RendererFeaturesMap = serializedObject.FindProperty(nameof(ScriptableRendererData.m_RendererFeatureMap));
  38. var editorObj = new SerializedObject(this);
  39. m_FalseBool = editorObj.FindProperty(nameof(falseBool));
  40. UpdateEditorList();
  41. }
  42. private void OnDisable()
  43. {
  44. ClearEditorsList();
  45. }
  46. public override void OnInspectorGUI()
  47. {
  48. if (m_RendererFeatures == null)
  49. OnEnable();
  50. else if (m_RendererFeatures.arraySize != m_Editors.Count)
  51. UpdateEditorList();
  52. serializedObject.Update();
  53. DrawRendererFeatureList();
  54. }
  55. private void DrawRendererFeatureList()
  56. {
  57. EditorGUILayout.LabelField(Styles.RenderFeatures, EditorStyles.boldLabel);
  58. EditorGUILayout.Space();
  59. if (m_RendererFeatures.arraySize == 0)
  60. {
  61. EditorGUILayout.HelpBox("No Renderer Features added", MessageType.Info);
  62. }
  63. else
  64. {
  65. //Draw List
  66. CoreEditorUtils.DrawSplitter();
  67. for (int i = 0; i < m_RendererFeatures.arraySize; i++)
  68. {
  69. SerializedProperty renderFeaturesProperty = m_RendererFeatures.GetArrayElementAtIndex(i);
  70. DrawRendererFeature(i, ref renderFeaturesProperty);
  71. CoreEditorUtils.DrawSplitter();
  72. }
  73. }
  74. EditorGUILayout.Space();
  75. //Add renderer
  76. if (GUILayout.Button("Add Renderer Feature", EditorStyles.miniButton))
  77. {
  78. AddPassMenu();
  79. }
  80. }
  81. private bool GetCustomTitle(Type type, out string title)
  82. {
  83. var isSingleFeature = type.GetCustomAttribute<DisallowMultipleRendererFeature>();
  84. if (isSingleFeature != null)
  85. {
  86. title = isSingleFeature.customTitle;
  87. return title != null;
  88. }
  89. title = null;
  90. return false;
  91. }
  92. private bool GetTooltip(Type type, out string tooltip)
  93. {
  94. var attribute = type.GetCustomAttribute<TooltipAttribute>();
  95. if (attribute != null)
  96. {
  97. tooltip = attribute.tooltip;
  98. return true;
  99. }
  100. tooltip = string.Empty;
  101. return false;
  102. }
  103. private void DrawRendererFeature(int index, ref SerializedProperty renderFeatureProperty)
  104. {
  105. Object rendererFeatureObjRef = renderFeatureProperty.objectReferenceValue;
  106. if (rendererFeatureObjRef != null)
  107. {
  108. bool hasChangedProperties = false;
  109. string title;
  110. bool hasCustomTitle = GetCustomTitle(rendererFeatureObjRef.GetType(), out title);
  111. if (!hasCustomTitle)
  112. {
  113. title = ObjectNames.GetInspectorTitle(rendererFeatureObjRef);
  114. }
  115. string tooltip;
  116. GetTooltip(rendererFeatureObjRef.GetType(), out tooltip);
  117. // Get the serialized object for the editor script & update it
  118. Editor rendererFeatureEditor = m_Editors[index];
  119. SerializedObject serializedRendererFeaturesEditor = rendererFeatureEditor.serializedObject;
  120. serializedRendererFeaturesEditor.Update();
  121. // Foldout header
  122. EditorGUI.BeginChangeCheck();
  123. SerializedProperty activeProperty = serializedRendererFeaturesEditor.FindProperty("m_Active");
  124. bool displayContent = CoreEditorUtils.DrawHeaderToggle(EditorGUIUtility.TrTextContent(title, tooltip), renderFeatureProperty, activeProperty, pos => OnContextClick(pos, index));
  125. hasChangedProperties |= EditorGUI.EndChangeCheck();
  126. // ObjectEditor
  127. if (displayContent)
  128. {
  129. if (!hasCustomTitle)
  130. {
  131. EditorGUI.BeginChangeCheck();
  132. SerializedProperty nameProperty = serializedRendererFeaturesEditor.FindProperty("m_Name");
  133. nameProperty.stringValue = ValidateName(EditorGUILayout.DelayedTextField(Styles.PassNameField, nameProperty.stringValue));
  134. if (EditorGUI.EndChangeCheck())
  135. {
  136. hasChangedProperties = true;
  137. // We need to update sub-asset name
  138. rendererFeatureObjRef.name = nameProperty.stringValue;
  139. AssetDatabase.SaveAssets();
  140. // Triggers update for sub-asset name change
  141. ProjectWindowUtil.ShowCreatedAsset(target);
  142. }
  143. }
  144. EditorGUI.BeginChangeCheck();
  145. rendererFeatureEditor.OnInspectorGUI();
  146. hasChangedProperties |= EditorGUI.EndChangeCheck();
  147. EditorGUILayout.Space(EditorGUIUtility.singleLineHeight);
  148. }
  149. // Apply changes and save if the user has modified any settings
  150. if (hasChangedProperties)
  151. {
  152. serializedRendererFeaturesEditor.ApplyModifiedProperties();
  153. serializedObject.ApplyModifiedProperties();
  154. ForceSave();
  155. }
  156. }
  157. else
  158. {
  159. CoreEditorUtils.DrawHeaderToggle(Styles.MissingFeature, renderFeatureProperty, m_FalseBool, pos => OnContextClick(pos, index));
  160. m_FalseBool.boolValue = false; // always make sure false bool is false
  161. EditorGUILayout.HelpBox(Styles.MissingFeature.tooltip, MessageType.Error);
  162. if (GUILayout.Button("Attempt Fix", EditorStyles.miniButton))
  163. {
  164. ScriptableRendererData data = target as ScriptableRendererData;
  165. data.ValidateRendererFeatures();
  166. }
  167. }
  168. }
  169. private void OnContextClick(Vector2 position, int id)
  170. {
  171. var menu = new GenericMenu();
  172. if (id == 0)
  173. menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Move Up"));
  174. else
  175. menu.AddItem(EditorGUIUtility.TrTextContent("Move Up"), false, () => MoveComponent(id, -1));
  176. if (id == m_RendererFeatures.arraySize - 1)
  177. menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Move Down"));
  178. else
  179. menu.AddItem(EditorGUIUtility.TrTextContent("Move Down"), false, () => MoveComponent(id, 1));
  180. menu.AddSeparator(string.Empty);
  181. menu.AddItem(EditorGUIUtility.TrTextContent("Remove"), false, () => RemoveComponent(id));
  182. menu.DropDown(new Rect(position, Vector2.zero));
  183. }
  184. private void AddPassMenu()
  185. {
  186. GenericMenu menu = new GenericMenu();
  187. TypeCache.TypeCollection types = TypeCache.GetTypesDerivedFrom<ScriptableRendererFeature>();
  188. foreach (Type type in types)
  189. {
  190. var data = target as ScriptableRendererData;
  191. if (data.DuplicateFeatureCheck(type))
  192. {
  193. continue;
  194. }
  195. string path = GetMenuNameFromType(type);
  196. menu.AddItem(new GUIContent(path), false, AddComponent, type.Name);
  197. }
  198. menu.ShowAsContext();
  199. }
  200. private void AddComponent(object type)
  201. {
  202. serializedObject.Update();
  203. ScriptableObject component = CreateInstance((string)type);
  204. component.name = $"{(string)type}";
  205. Undo.RegisterCreatedObjectUndo(component, "Add Renderer Feature");
  206. // Store this new effect as a sub-asset so we can reference it safely afterwards
  207. // Only when we're not dealing with an instantiated asset
  208. if (EditorUtility.IsPersistent(target))
  209. {
  210. AssetDatabase.AddObjectToAsset(component, target);
  211. }
  212. AssetDatabase.TryGetGUIDAndLocalFileIdentifier(component, out var guid, out long localId);
  213. // Grow the list first, then add - that's how serialized lists work in Unity
  214. m_RendererFeatures.arraySize++;
  215. SerializedProperty componentProp = m_RendererFeatures.GetArrayElementAtIndex(m_RendererFeatures.arraySize - 1);
  216. componentProp.objectReferenceValue = component;
  217. // Update GUID Map
  218. m_RendererFeaturesMap.arraySize++;
  219. SerializedProperty guidProp = m_RendererFeaturesMap.GetArrayElementAtIndex(m_RendererFeaturesMap.arraySize - 1);
  220. guidProp.longValue = localId;
  221. UpdateEditorList();
  222. serializedObject.ApplyModifiedProperties();
  223. // Force save / refresh
  224. if (EditorUtility.IsPersistent(target))
  225. {
  226. ForceSave();
  227. }
  228. serializedObject.ApplyModifiedProperties();
  229. }
  230. private void RemoveComponent(int id)
  231. {
  232. SerializedProperty property = m_RendererFeatures.GetArrayElementAtIndex(id);
  233. Object component = property.objectReferenceValue;
  234. property.objectReferenceValue = null;
  235. Undo.SetCurrentGroupName(component == null ? "Remove Renderer Feature" : $"Remove {component.name}");
  236. // remove the array index itself from the list
  237. m_RendererFeatures.DeleteArrayElementAtIndex(id);
  238. m_RendererFeaturesMap.DeleteArrayElementAtIndex(id);
  239. UpdateEditorList();
  240. serializedObject.ApplyModifiedProperties();
  241. // Destroy the setting object after ApplyModifiedProperties(). If we do it before, redo
  242. // actions will be in the wrong order and the reference to the setting object in the
  243. // list will be lost.
  244. if (component != null)
  245. {
  246. Undo.DestroyObjectImmediate(component);
  247. ScriptableRendererFeature feature = component as ScriptableRendererFeature;
  248. feature?.Dispose();
  249. }
  250. // Force save / refresh
  251. ForceSave();
  252. }
  253. private void MoveComponent(int id, int offset)
  254. {
  255. Undo.SetCurrentGroupName("Move Render Feature");
  256. serializedObject.Update();
  257. m_RendererFeatures.MoveArrayElement(id, id + offset);
  258. m_RendererFeaturesMap.MoveArrayElement(id, id + offset);
  259. UpdateEditorList();
  260. serializedObject.ApplyModifiedProperties();
  261. // Force save / refresh
  262. ForceSave();
  263. }
  264. private string GetMenuNameFromType(Type type)
  265. {
  266. string path;
  267. if (!GetCustomTitle(type, out path))
  268. {
  269. path = ObjectNames.NicifyVariableName(type.Name);
  270. }
  271. if (type.Namespace != null)
  272. {
  273. if (type.Namespace.Contains("Experimental"))
  274. path += " (Experimental)";
  275. }
  276. return path;
  277. }
  278. private string ValidateName(string name)
  279. {
  280. name = Regex.Replace(name, @"[^a-zA-Z0-9 ]", "");
  281. return name;
  282. }
  283. private void UpdateEditorList()
  284. {
  285. ClearEditorsList();
  286. for (int i = 0; i < m_RendererFeatures.arraySize; i++)
  287. {
  288. m_Editors.Add(CreateEditor(m_RendererFeatures.GetArrayElementAtIndex(i).objectReferenceValue));
  289. }
  290. }
  291. //To avoid leaking memory we destroy editors when we clear editors list
  292. private void ClearEditorsList()
  293. {
  294. for (int i = m_Editors.Count - 1; i >= 0; --i)
  295. {
  296. DestroyImmediate(m_Editors[i]);
  297. }
  298. m_Editors.Clear();
  299. }
  300. private void ForceSave()
  301. {
  302. EditorUtility.SetDirty(target);
  303. }
  304. }
  305. }