123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- using System;
- using System.Text;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.IO.IsolatedStorage;
- using System.Linq;
- using System.Text.RegularExpressions;
- using UnityEditor.Graphing;
- using UnityEditor.Graphing.Util;
- using UnityEditorInternal;
- using Debug = UnityEngine.Debug;
- using System.Reflection;
- using System.Runtime.Remoting.Metadata.W3cXsd2001;
- using UnityEditor.ProjectWindowCallback;
- using UnityEditor.ShaderGraph.Internal;
- using UnityEngine;
- using UnityEngine.Rendering;
- using Object = System.Object;
-
- namespace UnityEditor.ShaderGraph
- {
- // a structure used to track active variable dependencies in the shader code
- // (i.e. the use of uv0 in the pixel shader means we need a uv0 interpolator, etc.)
- struct Dependency
- {
- public string name; // the name of the thing
- public string dependsOn; // the thing above depends on this -- it reads it / calls it / requires it to be defined
-
- public Dependency(string name, string dependsOn)
- {
- this.name = name;
- this.dependsOn = dependsOn;
- }
- };
-
- [System.AttributeUsage(System.AttributeTargets.Struct)]
- class InterpolatorPack : System.Attribute
- {
- public InterpolatorPack()
- {
- }
- }
-
- // attribute used to flag a field as needing an HLSL semantic applied
- // i.e. float3 position : POSITION;
- // ^ semantic
- [System.AttributeUsage(System.AttributeTargets.Field)]
- class Semantic : System.Attribute
- {
- public string semantic;
-
- public Semantic(string semantic)
- {
- this.semantic = semantic;
- }
- }
-
- // attribute used to flag a field as being optional
- // i.e. if it is not active, then we can omit it from the struct
- [System.AttributeUsage(System.AttributeTargets.Field)]
- class Optional : System.Attribute
- {
- public Optional()
- {
- }
- }
-
- // attribute used to override the HLSL type of a field with a custom type string
- [System.AttributeUsage(System.AttributeTargets.Field)]
- class OverrideType : System.Attribute
- {
- public string typeName;
-
- public OverrideType(string typeName)
- {
- this.typeName = typeName;
- }
- }
-
- // attribute used to force system generated fields to bottom of structs
- [System.AttributeUsage(System.AttributeTargets.Field)]
- class SystemGenerated : System.Attribute
- {
- public SystemGenerated()
- {
- }
- }
-
- // attribute used to disable a field using a preprocessor #if
- [System.AttributeUsage(System.AttributeTargets.Field)]
- class PreprocessorIf : System.Attribute
- {
- public string conditional;
-
- public PreprocessorIf(string conditional)
- {
- this.conditional = conditional;
- }
- }
-
- class NewGraphAction : EndNameEditAction
- {
- Target[] m_Targets;
- public Target[] targets
- {
- get => m_Targets;
- set => m_Targets = value;
- }
-
- BlockFieldDescriptor[] m_Blocks;
- public BlockFieldDescriptor[] blocks
- {
- get => m_Blocks;
- set => m_Blocks = value;
- }
-
- public override void Action(int instanceId, string pathName, string resourceFile)
- {
- var graph = new GraphData();
- graph.AddContexts();
- graph.InitializeOutputs(m_Targets, m_Blocks);
- graph.AddCategory(CategoryData.DefaultCategory());
-
- graph.path = "Shader Graphs";
- FileUtilities.WriteShaderGraphToDisk(pathName, graph);
- AssetDatabase.Refresh();
-
- UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<Shader>(pathName);
- Selection.activeObject = obj;
- }
- }
-
- static class GraphUtil
- {
- internal static bool CheckForRecursiveDependencyOnPendingSave(string saveFilePath, IEnumerable<SubGraphNode> subGraphNodes, string context = null)
- {
- var overwriteGUID = AssetDatabase.AssetPathToGUID(saveFilePath);
- if (!string.IsNullOrEmpty(overwriteGUID))
- {
- foreach (var sgNode in subGraphNodes)
- {
- var asset = sgNode?.asset;
- if (asset == null)
- {
- // cannot read the asset; might be recursive but we can't tell... should we return "maybe"?
- // I think to be minimally intrusive to the user we can assume "No" in this case,
- // even though this may miss recursions in extraordinary cases.
- // it's more important to allow the user to save their files than to catch 100% of recursions
- continue;
- }
- else if ((asset.assetGuid == overwriteGUID) || asset.descendents.Contains(overwriteGUID))
- {
- if (context != null)
- {
- Debug.LogWarning(context + " CANCELLED to avoid a generating a reference loop: the SubGraph '" + sgNode.asset.name + "' references the target file '" + saveFilePath + "'");
- EditorUtility.DisplayDialog(
- context + " CANCELLED",
- "Saving the file would generate a reference loop, because the SubGraph '" + sgNode.asset.name + "' references the target file '" + saveFilePath + "'", "Cancel");
- }
- return true;
- }
- }
- }
- return false;
- }
-
- internal static string ConvertCamelCase(string text, bool preserveAcronyms)
- {
- if (string.IsNullOrEmpty(text))
- return string.Empty;
- StringBuilder newText = new StringBuilder(text.Length * 2);
- newText.Append(text[0]);
- for (int i = 1; i < text.Length; i++)
- {
- if (char.IsUpper(text[i]))
- if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
- (preserveAcronyms && char.IsUpper(text[i - 1]) &&
- i < text.Length - 1 && !char.IsUpper(text[i + 1])))
- newText.Append(' ');
- newText.Append(text[i]);
- }
- return newText.ToString();
- }
-
- public static void CreateNewGraph()
- {
- var graphItem = ScriptableObject.CreateInstance<NewGraphAction>();
- graphItem.targets = null;
- ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, graphItem,
- string.Format("New Shader Graph.{0}", ShaderGraphImporter.Extension), null, null);
- }
-
- public static void CreateNewGraphWithOutputs(Target[] targets, BlockFieldDescriptor[] blockDescriptors)
- {
- var graphItem = ScriptableObject.CreateInstance<NewGraphAction>();
- graphItem.targets = targets;
- graphItem.blocks = blockDescriptors;
- ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, graphItem,
- string.Format("New Shader Graph.{0}", ShaderGraphImporter.Extension), null, null);
- }
-
- public static bool TryGetMetadataOfType<T>(this Shader shader, out T obj) where T : ScriptableObject
- {
- obj = null;
- if (!shader.IsShaderGraphAsset())
- return false;
-
- var path = AssetDatabase.GetAssetPath(shader);
- foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(path))
- {
- if (asset is T metadataAsset)
- {
- obj = metadataAsset;
- return true;
- }
- }
-
- return false;
- }
-
- // this will work on ALL shadergraph-built shaders, in memory or asset based
- public static bool IsShaderGraph(this Material material)
- {
- var shaderGraphTag = material.GetTag("ShaderGraphShader", false, null);
- return !string.IsNullOrEmpty(shaderGraphTag);
- }
-
- // NOTE: this ONLY works for ASSET based Shaders, if you created a temporary shader in memory, it won't work
- public static bool IsShaderGraphAsset(this Shader shader)
- {
- var path = AssetDatabase.GetAssetPath(shader);
- var importer = AssetImporter.GetAtPath(path);
- return importer is ShaderGraphImporter;
- }
-
- [Obsolete("Use IsShaderGraphAsset instead", false)]
- public static bool IsShaderGraph(this Shader shader) => shader.IsShaderGraphAsset();
-
- static void Visit(List<AbstractMaterialNode> outputList, Dictionary<string, AbstractMaterialNode> unmarkedNodes, AbstractMaterialNode node)
- {
- if (!unmarkedNodes.ContainsKey(node.objectId))
- return;
- foreach (var slot in node.GetInputSlots<MaterialSlot>())
- {
- foreach (var edge in node.owner.GetEdges(slot.slotReference))
- {
- var inputNode = edge.outputSlot.node;
- Visit(outputList, unmarkedNodes, inputNode);
- }
- }
- unmarkedNodes.Remove(node.objectId);
- outputList.Add(node);
- }
-
- static Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo> s_LegacyTypeRemapping;
-
- public static Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo> GetLegacyTypeRemapping()
- {
- if (s_LegacyTypeRemapping == null)
- {
- s_LegacyTypeRemapping = new Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo>();
- foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
- {
- foreach (var type in assembly.GetTypesOrNothing())
- {
- if (type.IsAbstract)
- continue;
- foreach (var attribute in type.GetCustomAttributes(typeof(FormerNameAttribute), false))
- {
- var legacyAttribute = (FormerNameAttribute)attribute;
- var serializationInfo = new SerializationHelper.TypeSerializationInfo { fullName = legacyAttribute.fullName };
- s_LegacyTypeRemapping[serializationInfo] = SerializationHelper.GetTypeSerializableAsString(type);
- }
- }
- }
- }
-
- return s_LegacyTypeRemapping;
- }
-
- /// <summary>
- /// Sanitizes a supplied string such that it does not collide
- /// with any other name in a collection.
- /// </summary>
- /// <param name="existingNames">
- /// A collection of names that the new name should not collide with.
- /// </param>
- /// <param name="duplicateFormat">
- /// The format applied to the name if a duplicate exists.
- /// This must be a format string that contains `{0}` and `{1}`
- /// once each. An example could be `{0} ({1})`, which will append ` (n)`
- /// to the name for the n`th duplicate.
- /// </param>
- /// <param name="name">
- /// The name to be sanitized.
- /// </param>
- /// <returns>
- /// A name that is distinct form any name in `existingNames`.
- /// </returns>
- internal static string SanitizeName(IEnumerable<string> existingNames, string duplicateFormat, string name, string disallowedPatternRegex = "\"")
- {
- name = Regex.Replace(name, disallowedPatternRegex, "_");
- return DeduplicateName(existingNames, duplicateFormat, name);
- }
-
- internal static string SanitizeCategoryName(string categoryName, string disallowedPatternRegex = "\"")
- {
- return Regex.Replace(categoryName, disallowedPatternRegex, "_");
- }
-
- internal static string DeduplicateName(IEnumerable<string> existingNames, string duplicateFormat, string name)
- {
- if (!existingNames.Contains(name))
- return name;
-
- string escapedDuplicateFormat = Regex.Escape(duplicateFormat);
-
- // Escaped format will escape string interpolation, so the escape characters must be removed for these.
- escapedDuplicateFormat = escapedDuplicateFormat.Replace(@"\{0}", @"{0}");
- escapedDuplicateFormat = escapedDuplicateFormat.Replace(@"\{1}", @"{1}");
-
- var baseRegex = new Regex(string.Format(escapedDuplicateFormat, @"^(.*)", @"(\d+)"));
-
- var baseMatch = baseRegex.Match(name);
- if (baseMatch.Success)
- name = baseMatch.Groups[1].Value;
-
- string baseNameExpression = string.Format(@"^{0}", Regex.Escape(name));
- var regex = new Regex(string.Format(escapedDuplicateFormat, baseNameExpression, @"(\d+)") + "$");
-
- var existingDuplicateNumbers = existingNames.Select(existingName => regex.Match(existingName)).Where(m => m.Success).Select(m => int.Parse(m.Groups[1].Value)).Where(n => n > 0).Distinct().ToList();
-
- var duplicateNumber = 1;
- existingDuplicateNumbers.Sort();
- if (existingDuplicateNumbers.Any() && existingDuplicateNumbers.First() == 1)
- {
- duplicateNumber = existingDuplicateNumbers.Last() + 1;
- for (var i = 1; i < existingDuplicateNumbers.Count; i++)
- {
- if (existingDuplicateNumbers[i - 1] != existingDuplicateNumbers[i] - 1)
- {
- duplicateNumber = existingDuplicateNumbers[i - 1] + 1;
- break;
- }
- }
- }
-
- return string.Format(duplicateFormat, name, duplicateNumber);
- }
-
- public static bool WriteToFile(string path, string content)
- {
- try
- {
- File.WriteAllText(path, content);
- return true;
- }
- catch (Exception e)
- {
- Debug.LogError(e);
- return false;
- }
- }
-
- public static void OpenFile(string path)
- {
- string filePath = Path.GetFullPath(path);
- if (!File.Exists(filePath))
- {
- Debug.LogError(string.Format("Path {0} doesn't exists", path));
- return;
- }
-
- string externalScriptEditor = ScriptEditorUtility.GetExternalScriptEditor();
- if (externalScriptEditor != "internal")
- {
- InternalEditorUtility.OpenFileAtLineExternal(filePath, 0);
- }
- else
- {
- Process p = new Process();
- p.StartInfo.FileName = filePath;
- p.EnableRaisingEvents = true;
- p.Exited += (Object obj, EventArgs args) =>
- {
- if (p.ExitCode != 0)
- Debug.LogWarningFormat("Unable to open {0}: Check external editor in preferences", filePath);
- };
- p.Start();
- }
- }
-
- //
- // Find all nodes of the given type downstream from the given node
- // Returns a unique list. So even if a node can be reached through different paths it will be present only once.
- //
- public static List<NodeType> FindDownStreamNodesOfType<NodeType>(AbstractMaterialNode node) where NodeType : AbstractMaterialNode
- {
- // Should never be called without a node
- Debug.Assert(node != null);
-
- HashSet<AbstractMaterialNode> visitedNodes = new HashSet<AbstractMaterialNode>();
- List<NodeType> vtNodes = new List<NodeType>();
- Queue<AbstractMaterialNode> nodeStack = new Queue<AbstractMaterialNode>();
- nodeStack.Enqueue(node);
- visitedNodes.Add(node);
-
- while (nodeStack.Count > 0)
- {
- AbstractMaterialNode visit = nodeStack.Dequeue();
-
- // Flood fill through all the nodes
- foreach (var slot in visit.GetInputSlots<MaterialSlot>())
- {
- foreach (var edge in visit.owner.GetEdges(slot.slotReference))
- {
- var inputNode = edge.outputSlot.node;
- if (!visitedNodes.Contains(inputNode))
- {
- nodeStack.Enqueue(inputNode);
- visitedNodes.Add(inputNode);
- }
- }
- }
-
- // Extract vt node
- if (visit is NodeType)
- {
- NodeType vtNode = visit as NodeType;
- vtNodes.Add(vtNode);
- }
- }
-
- return vtNodes;
- }
- }
- }
|