Singularity/Library/PackageCache/com.unity.shadergraph@12.1.11/Editor/Data/Util/GraphUtil.cs
2024-05-06 11:45:45 -07:00

438 lines
17 KiB
C#

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.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;
}
}
}