using System; using System.Collections.Generic; using System.Linq; using UnityEditor.ShaderGraph.Internal; using UnityEngine; using static UnityEditor.ShaderGraph.CategoryDataCollection; using Object = UnityEngine.Object; namespace UnityEditor.ShaderGraph { [Serializable] struct GraphInputData { public string referenceName; public bool isKeyword; public PropertyType propertyType; public KeywordType keywordType; public bool isCompoundProperty; public List subProperties; public string tooltip; public override bool Equals(object obj) { if (obj is null || obj is not GraphInputData o) return false; if (subProperties is null != o.subProperties is null || referenceName is null != o.referenceName is null || tooltip is null != o.tooltip is null || referenceName is not null && !referenceName.Equals(o.referenceName) || !isKeyword.Equals(o.isKeyword) || !propertyType.Equals(o.propertyType) || !keywordType.Equals(o.keywordType) || !isCompoundProperty.Equals(o.isCompoundProperty) // || tooltip is not null && !tooltip.Equals(o.tooltip) || subProperties is not null && !subProperties.Count.Equals(o.subProperties.Count)) return false; if (subProperties is not null) for (int i = 0; i < subProperties.Count; ++i) if (!subProperties[i].Equals(o.subProperties[i])) return false; return true; } public override int GetHashCode() { var hash = new HashCode(); hash.Add(referenceName); hash.Add(isKeyword); hash.Add(propertyType); hash.Add(keywordType); hash.Add(isCompoundProperty); // hash.Add(tooltip); This is not part of the graph inputs identity. if (subProperties is not null) foreach (var subproperty in subProperties) { hash.Add(subproperty.referenceName); hash.Add(subproperty.propertyType); } return hash.ToHashCode(); } } [Serializable] struct SubPropertyData { public string referenceName; public PropertyType propertyType; } [Serializable] class MinimalCategoryData { // ShaderLab doesn't understand virtual texture inputs, they need to be processed to replace the virtual texture input with the layers that compose it instead public static GraphInputData ProcessVirtualTextureProperty(VirtualTextureShaderProperty virtualTextureShaderProperty) { var layerReferenceNames = new List(); virtualTextureShaderProperty.GetPropertyReferenceNames(layerReferenceNames); var virtualTextureLayerDataList = new List(); // Skip the first entry in this list as that's the Virtual Texture reference name itself, which won't exist in ShaderLab foreach (var referenceName in layerReferenceNames.Skip(1)) { var layerPropertyData = new SubPropertyData() { referenceName = referenceName, propertyType = PropertyType.Texture2D }; virtualTextureLayerDataList.Add(layerPropertyData); } var virtualTexturePropertyData = new GraphInputData() { referenceName = virtualTextureShaderProperty.displayName, propertyType = PropertyType.VirtualTexture, isKeyword = false }; virtualTexturePropertyData.isCompoundProperty = true; virtualTexturePropertyData.subProperties = virtualTextureLayerDataList; return virtualTexturePropertyData; } public static bool TryProcessInput(ShaderInput input, out GraphInputData data) { data = default; if (!input.isExposed) return false; string tooltip = ""; if (input.promoteToFinalShader) tooltip = $"from {input.PromotedAssetPath}"; switch(input) { case VirtualTextureShaderProperty vt: data = ProcessVirtualTextureProperty(vt); return true; case AbstractShaderProperty prop: data = new GraphInputData() { referenceName = prop.referenceName, propertyType = prop.propertyType, isKeyword = false, tooltip = tooltip }; return true; case ShaderKeyword keyword: var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); data = new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true, tooltip = tooltip }; return true; default: return false; } } public string categoryName; public List propertyDatas; [NonSerialized] public bool expanded = true; } class ShaderGraphMetadata : ScriptableObject { public string outputNodeTypeName; // these asset dependencies are stored here as a way for "Export Package..." to discover them // and automatically pull them in to the .unitypackage public List assetDependencies; public List categoryDatas; } internal class CategoryDataCollection { HashSet<(GraphInputData input, string category)> lookup = new(); Dictionary categoryScores = new(); Dictionary propertyScores = new(); internal void Set(string categoryName, GraphInputData data, int propertyScore = 0, int categoryScore = 0) { if (!categoryScores.TryAdd(categoryName, categoryScore) && categoryScore < categoryScores[categoryName]) categoryScores[categoryName] = categoryScore; if (!propertyScores.TryAdd(data, propertyScore) && propertyScore < propertyScores[data]) propertyScores[data] = propertyScore; // append the tooltip if it doesn't already exist. if (lookup.TryGetValue((data, categoryName), out var currentEntry) && !currentEntry.input.tooltip.Contains(data.tooltip)) { lookup.Remove((data, categoryName)); currentEntry.input.tooltip += $"\n{data.tooltip}"; lookup.Add(currentEntry); } else lookup.Add((data, categoryName)); } internal List GenerateMCD() { var result = new List(); var buckets = new Dictionary>(); var order = new List(categoryScores.Keys); order.Sort((a, b) => { var score = categoryScores[a].CompareTo(categoryScores[b]); if (score == 0) score = a.CompareTo(b); return score; }); foreach(var data in lookup) { buckets.TryAdd(data.category, new()); buckets[data.category].Add(data.input); } foreach(var name in order) { if (!buckets.ContainsKey(name)) continue; buckets[name].Sort((a, b) => { var score = propertyScores[a].CompareTo(propertyScores[b]); if (score == 0) score = a.referenceName.CompareTo(b.referenceName); return score; }); result.Add(new() { categoryName = name, propertyDatas = buckets[name] }); } return result; } } }