You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
14 KiB
361 lines
14 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Reflection; |
|
using System.Text.RegularExpressions; |
|
using UnityEngine; |
|
using UnityEngine.Rendering.Universal; |
|
using Object = UnityEngine.Object; |
|
|
|
namespace UnityEditor.Rendering.Universal |
|
{ |
|
[CustomEditor(typeof(ScriptableRendererData), true)] |
|
public class ScriptableRendererDataEditor : Editor |
|
{ |
|
class Styles |
|
{ |
|
public static readonly GUIContent RenderFeatures = |
|
new GUIContent("Renderer Features", |
|
"A Renderer Feature is an asset that lets you add extra Render passes to a URP Renderer and configure their behavior."); |
|
|
|
public static readonly GUIContent PassNameField = |
|
new GUIContent("Name", "Render pass name. This name is the name displayed in Frame Debugger."); |
|
|
|
public static readonly GUIContent MissingFeature = new GUIContent("Missing RendererFeature", |
|
"Missing reference, due to compilation issues or missing files. you can attempt auto fix or choose to remove the feature."); |
|
|
|
public static GUIStyle BoldLabelSimple; |
|
|
|
static Styles() |
|
{ |
|
BoldLabelSimple = new GUIStyle(EditorStyles.label); |
|
BoldLabelSimple.fontStyle = FontStyle.Bold; |
|
} |
|
} |
|
|
|
private SerializedProperty m_RendererFeatures; |
|
private SerializedProperty m_RendererFeaturesMap; |
|
private SerializedProperty m_FalseBool; |
|
[SerializeField] private bool falseBool = false; |
|
List<Editor> m_Editors = new List<Editor>(); |
|
|
|
private void OnEnable() |
|
{ |
|
m_RendererFeatures = serializedObject.FindProperty(nameof(ScriptableRendererData.m_RendererFeatures)); |
|
m_RendererFeaturesMap = serializedObject.FindProperty(nameof(ScriptableRendererData.m_RendererFeatureMap)); |
|
var editorObj = new SerializedObject(this); |
|
m_FalseBool = editorObj.FindProperty(nameof(falseBool)); |
|
UpdateEditorList(); |
|
} |
|
|
|
private void OnDisable() |
|
{ |
|
ClearEditorsList(); |
|
} |
|
|
|
public override void OnInspectorGUI() |
|
{ |
|
if (m_RendererFeatures == null) |
|
OnEnable(); |
|
else if (m_RendererFeatures.arraySize != m_Editors.Count) |
|
UpdateEditorList(); |
|
|
|
serializedObject.Update(); |
|
DrawRendererFeatureList(); |
|
} |
|
|
|
private void DrawRendererFeatureList() |
|
{ |
|
EditorGUILayout.LabelField(Styles.RenderFeatures, EditorStyles.boldLabel); |
|
EditorGUILayout.Space(); |
|
|
|
if (m_RendererFeatures.arraySize == 0) |
|
{ |
|
EditorGUILayout.HelpBox("No Renderer Features added", MessageType.Info); |
|
} |
|
else |
|
{ |
|
//Draw List |
|
CoreEditorUtils.DrawSplitter(); |
|
for (int i = 0; i < m_RendererFeatures.arraySize; i++) |
|
{ |
|
SerializedProperty renderFeaturesProperty = m_RendererFeatures.GetArrayElementAtIndex(i); |
|
DrawRendererFeature(i, ref renderFeaturesProperty); |
|
CoreEditorUtils.DrawSplitter(); |
|
} |
|
} |
|
EditorGUILayout.Space(); |
|
|
|
//Add renderer |
|
if (GUILayout.Button("Add Renderer Feature", EditorStyles.miniButton)) |
|
{ |
|
AddPassMenu(); |
|
} |
|
} |
|
|
|
private bool GetCustomTitle(Type type, out string title) |
|
{ |
|
var isSingleFeature = type.GetCustomAttribute<DisallowMultipleRendererFeature>(); |
|
if (isSingleFeature != null) |
|
{ |
|
title = isSingleFeature.customTitle; |
|
return title != null; |
|
} |
|
title = null; |
|
return false; |
|
} |
|
|
|
private bool GetTooltip(Type type, out string tooltip) |
|
{ |
|
var attribute = type.GetCustomAttribute<TooltipAttribute>(); |
|
if (attribute != null) |
|
{ |
|
tooltip = attribute.tooltip; |
|
return true; |
|
} |
|
tooltip = string.Empty; |
|
return false; |
|
} |
|
|
|
private void DrawRendererFeature(int index, ref SerializedProperty renderFeatureProperty) |
|
{ |
|
Object rendererFeatureObjRef = renderFeatureProperty.objectReferenceValue; |
|
if (rendererFeatureObjRef != null) |
|
{ |
|
bool hasChangedProperties = false; |
|
string title; |
|
|
|
bool hasCustomTitle = GetCustomTitle(rendererFeatureObjRef.GetType(), out title); |
|
|
|
if (!hasCustomTitle) |
|
{ |
|
title = ObjectNames.GetInspectorTitle(rendererFeatureObjRef); |
|
} |
|
|
|
string tooltip; |
|
GetTooltip(rendererFeatureObjRef.GetType(), out tooltip); |
|
|
|
// Get the serialized object for the editor script & update it |
|
Editor rendererFeatureEditor = m_Editors[index]; |
|
SerializedObject serializedRendererFeaturesEditor = rendererFeatureEditor.serializedObject; |
|
serializedRendererFeaturesEditor.Update(); |
|
|
|
|
|
// Foldout header |
|
EditorGUI.BeginChangeCheck(); |
|
SerializedProperty activeProperty = serializedRendererFeaturesEditor.FindProperty("m_Active"); |
|
bool displayContent = CoreEditorUtils.DrawHeaderToggle(EditorGUIUtility.TrTextContent(title, tooltip), renderFeatureProperty, activeProperty, pos => OnContextClick(pos, index)); |
|
hasChangedProperties |= EditorGUI.EndChangeCheck(); |
|
|
|
// ObjectEditor |
|
if (displayContent) |
|
{ |
|
if (!hasCustomTitle) |
|
{ |
|
EditorGUI.BeginChangeCheck(); |
|
SerializedProperty nameProperty = serializedRendererFeaturesEditor.FindProperty("m_Name"); |
|
nameProperty.stringValue = ValidateName(EditorGUILayout.DelayedTextField(Styles.PassNameField, nameProperty.stringValue)); |
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
hasChangedProperties = true; |
|
|
|
// We need to update sub-asset name |
|
rendererFeatureObjRef.name = nameProperty.stringValue; |
|
AssetDatabase.SaveAssets(); |
|
|
|
// Triggers update for sub-asset name change |
|
ProjectWindowUtil.ShowCreatedAsset(target); |
|
} |
|
} |
|
|
|
EditorGUI.BeginChangeCheck(); |
|
rendererFeatureEditor.OnInspectorGUI(); |
|
hasChangedProperties |= EditorGUI.EndChangeCheck(); |
|
|
|
EditorGUILayout.Space(EditorGUIUtility.singleLineHeight); |
|
} |
|
|
|
// Apply changes and save if the user has modified any settings |
|
if (hasChangedProperties) |
|
{ |
|
serializedRendererFeaturesEditor.ApplyModifiedProperties(); |
|
serializedObject.ApplyModifiedProperties(); |
|
ForceSave(); |
|
} |
|
} |
|
else |
|
{ |
|
CoreEditorUtils.DrawHeaderToggle(Styles.MissingFeature, renderFeatureProperty, m_FalseBool, pos => OnContextClick(pos, index)); |
|
m_FalseBool.boolValue = false; // always make sure false bool is false |
|
EditorGUILayout.HelpBox(Styles.MissingFeature.tooltip, MessageType.Error); |
|
if (GUILayout.Button("Attempt Fix", EditorStyles.miniButton)) |
|
{ |
|
ScriptableRendererData data = target as ScriptableRendererData; |
|
data.ValidateRendererFeatures(); |
|
} |
|
} |
|
} |
|
|
|
private void OnContextClick(Vector2 position, int id) |
|
{ |
|
var menu = new GenericMenu(); |
|
|
|
if (id == 0) |
|
menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Move Up")); |
|
else |
|
menu.AddItem(EditorGUIUtility.TrTextContent("Move Up"), false, () => MoveComponent(id, -1)); |
|
|
|
if (id == m_RendererFeatures.arraySize - 1) |
|
menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Move Down")); |
|
else |
|
menu.AddItem(EditorGUIUtility.TrTextContent("Move Down"), false, () => MoveComponent(id, 1)); |
|
|
|
menu.AddSeparator(string.Empty); |
|
menu.AddItem(EditorGUIUtility.TrTextContent("Remove"), false, () => RemoveComponent(id)); |
|
|
|
menu.DropDown(new Rect(position, Vector2.zero)); |
|
} |
|
|
|
private void AddPassMenu() |
|
{ |
|
GenericMenu menu = new GenericMenu(); |
|
TypeCache.TypeCollection types = TypeCache.GetTypesDerivedFrom<ScriptableRendererFeature>(); |
|
foreach (Type type in types) |
|
{ |
|
var data = target as ScriptableRendererData; |
|
if (data.DuplicateFeatureCheck(type)) |
|
{ |
|
continue; |
|
} |
|
|
|
string path = GetMenuNameFromType(type); |
|
menu.AddItem(new GUIContent(path), false, AddComponent, type.Name); |
|
} |
|
menu.ShowAsContext(); |
|
} |
|
|
|
private void AddComponent(object type) |
|
{ |
|
serializedObject.Update(); |
|
|
|
ScriptableObject component = CreateInstance((string)type); |
|
component.name = $"{(string)type}"; |
|
Undo.RegisterCreatedObjectUndo(component, "Add Renderer Feature"); |
|
|
|
// Store this new effect as a sub-asset so we can reference it safely afterwards |
|
// Only when we're not dealing with an instantiated asset |
|
if (EditorUtility.IsPersistent(target)) |
|
{ |
|
AssetDatabase.AddObjectToAsset(component, target); |
|
} |
|
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(component, out var guid, out long localId); |
|
|
|
// Grow the list first, then add - that's how serialized lists work in Unity |
|
m_RendererFeatures.arraySize++; |
|
SerializedProperty componentProp = m_RendererFeatures.GetArrayElementAtIndex(m_RendererFeatures.arraySize - 1); |
|
componentProp.objectReferenceValue = component; |
|
|
|
// Update GUID Map |
|
m_RendererFeaturesMap.arraySize++; |
|
SerializedProperty guidProp = m_RendererFeaturesMap.GetArrayElementAtIndex(m_RendererFeaturesMap.arraySize - 1); |
|
guidProp.longValue = localId; |
|
UpdateEditorList(); |
|
serializedObject.ApplyModifiedProperties(); |
|
|
|
// Force save / refresh |
|
if (EditorUtility.IsPersistent(target)) |
|
{ |
|
ForceSave(); |
|
} |
|
serializedObject.ApplyModifiedProperties(); |
|
} |
|
|
|
private void RemoveComponent(int id) |
|
{ |
|
SerializedProperty property = m_RendererFeatures.GetArrayElementAtIndex(id); |
|
Object component = property.objectReferenceValue; |
|
property.objectReferenceValue = null; |
|
|
|
Undo.SetCurrentGroupName(component == null ? "Remove Renderer Feature" : $"Remove {component.name}"); |
|
|
|
// remove the array index itself from the list |
|
m_RendererFeatures.DeleteArrayElementAtIndex(id); |
|
m_RendererFeaturesMap.DeleteArrayElementAtIndex(id); |
|
UpdateEditorList(); |
|
serializedObject.ApplyModifiedProperties(); |
|
|
|
// Destroy the setting object after ApplyModifiedProperties(). If we do it before, redo |
|
// actions will be in the wrong order and the reference to the setting object in the |
|
// list will be lost. |
|
if (component != null) |
|
{ |
|
Undo.DestroyObjectImmediate(component); |
|
|
|
ScriptableRendererFeature feature = component as ScriptableRendererFeature; |
|
feature?.Dispose(); |
|
} |
|
|
|
// Force save / refresh |
|
ForceSave(); |
|
} |
|
|
|
private void MoveComponent(int id, int offset) |
|
{ |
|
Undo.SetCurrentGroupName("Move Render Feature"); |
|
serializedObject.Update(); |
|
m_RendererFeatures.MoveArrayElement(id, id + offset); |
|
m_RendererFeaturesMap.MoveArrayElement(id, id + offset); |
|
UpdateEditorList(); |
|
serializedObject.ApplyModifiedProperties(); |
|
|
|
// Force save / refresh |
|
ForceSave(); |
|
} |
|
|
|
private string GetMenuNameFromType(Type type) |
|
{ |
|
string path; |
|
if (!GetCustomTitle(type, out path)) |
|
{ |
|
path = ObjectNames.NicifyVariableName(type.Name); |
|
} |
|
|
|
if (type.Namespace != null) |
|
{ |
|
if (type.Namespace.Contains("Experimental")) |
|
path += " (Experimental)"; |
|
} |
|
|
|
return path; |
|
} |
|
|
|
private string ValidateName(string name) |
|
{ |
|
name = Regex.Replace(name, @"[^a-zA-Z0-9 ]", ""); |
|
return name; |
|
} |
|
|
|
private void UpdateEditorList() |
|
{ |
|
ClearEditorsList(); |
|
for (int i = 0; i < m_RendererFeatures.arraySize; i++) |
|
{ |
|
m_Editors.Add(CreateEditor(m_RendererFeatures.GetArrayElementAtIndex(i).objectReferenceValue)); |
|
} |
|
} |
|
|
|
//To avoid leaking memory we destroy editors when we clear editors list |
|
private void ClearEditorsList() |
|
{ |
|
for (int i = m_Editors.Count - 1; i >= 0; --i) |
|
{ |
|
DestroyImmediate(m_Editors[i]); |
|
} |
|
m_Editors.Clear(); |
|
} |
|
|
|
private void ForceSave() |
|
{ |
|
EditorUtility.SetDirty(target); |
|
} |
|
} |
|
}
|
|
|