using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor.Graphing; using UnityEngine; namespace UnityEditor.ShaderGraph { sealed partial class GraphData : ISerializationCallbackReceiver { public static class GraphValidation { static Dictionary s_ActiveSubTarget = new(); static readonly PropertyInfo s_JsonData = typeof(Serialization.JsonData).GetProperty("value"); static SubTarget GetActiveSubTarget(Target target) { if (target.activeSubTarget != null && target.activeSubTarget.IsActive()) return target.activeSubTarget; var type = target.GetType(); if (!s_ActiveSubTarget.TryGetValue(type, out var activeSubTarget)) { activeSubTarget = type.GetField("m_ActiveSubTarget", BindingFlags.Instance | BindingFlags.NonPublic); s_ActiveSubTarget.Add(type, activeSubTarget); } if (activeSubTarget != null) { var jsonData = activeSubTarget.GetValue(target); if (jsonData != null) { return s_JsonData.GetValue(jsonData) as SubTarget; } } return null; } public static void ValidateNode(AbstractMaterialNode node) { Type t = node.GetType(); node.ValidateNode(); if (!(node is BlockNode)) { bool disallowedByAnyTargets = false; bool disallowedByAllTargets = true; bool disallowedByAnySubTarget = false; IEnumerable targets = node.owner.activeTargets; foreach (var target in targets) { var subtarget = GetActiveSubTarget(target); if (subtarget != null && (!subtarget.IsNodeAllowedBySubTarget(t) || !node.IsSubTargetCompatible(subtarget.GetType()))) { disallowedByAnySubTarget = true; node.isValid = false; node.owner.AddValidationError(node.objectId, $"{node.name} Node is not allowed by {subtarget.displayName} implementation", Rendering.ShaderCompilerMessageSeverity.Error); } //if at least one target doesn't allow a node, it is considered invalid else if (!target.IsNodeAllowedByTarget(t)) { disallowedByAnyTargets = true; node.isValid = false; node.owner.AddValidationError(node.objectId, $"{node.name} Node is not allowed by {target.displayName} implementation", Rendering.ShaderCompilerMessageSeverity.Warning); node.owner.m_UnsupportedTargets.Add(target); } //at least one target does allow node, not going to be explicitly set inactive else { disallowedByAllTargets = false; } if (subtarget != null && subtarget.ValidateNodeCompatibility(node, out string warningMessage, out Rendering.ShaderCompilerMessageSeverity severity)) { if (severity == Rendering.ShaderCompilerMessageSeverity.Error) { disallowedByAnySubTarget = true; node.isValid = false; } node.owner.AddValidationError(node.objectId, warningMessage, severity); } } // Subgraphs have no allegiance to a Target/SubTarget workflow, // but we can infer incompatibilities when SRPFilter and SubTargetFilter are // similarly incompatible across nodes in a subgraph. // Unfortunately, there isn't a good way to promote these compatibilities to // the main graph without quite a bit more work. if (node.owner.isSubGraph) { disallowedByAllTargets = false; System.Text.StringBuilder sb = new(); if (!node.IsCompatibleWithSRPs(srpSet, out var badSrps)) { disallowedByAnyTargets = true; sb.Append("Nodes in subgraph have conflicting SRP restrictions; "); foreach (var srp in badSrps) sb.Append($"{srp.Name}, "); sb.Remove(sb.Length - 2, 2); sb.AppendLine(); } if (!node.IsCompatibleWithSubTargetFilters(subTargetSet, out var badSubTargets)) { disallowedByAnySubTarget = true; sb.Append("Nodes in subgraph have conflicting Material restrictions; "); foreach (var subTarget in badSubTargets) sb.Append($"{subTarget.Name}, "); sb.Remove(sb.Length - 2, 2); } if (disallowedByAnySubTarget || disallowedByAnyTargets) { node.isValid = false; node.owner.AddValidationError(node.objectId, sb.ToString(), Rendering.ShaderCompilerMessageSeverity.Warning); } } if (!disallowedByAnyTargets && !disallowedByAnySubTarget) { node.isValid = true; } //Set ActiveState based on if all targets disallow this node if (disallowedByAllTargets) { node.SetOverrideActiveState(AbstractMaterialNode.ActiveState.ExplicitInactive); node.owner.AddValidationError(node.objectId, $"{node.name} Node is not allowed by any active targets, and will not be used in generation", Rendering.ShaderCompilerMessageSeverity.Warning); } else { node.SetOverrideActiveState(AbstractMaterialNode.ActiveState.Implicit); } } } static HashSet srpSet; static HashSet subTargetSet; public static void ValidateGraph(GraphData graph) { graph.m_UnsupportedTargets.Clear(); srpSet = new(); subTargetSet = new(); if (graph.isSubGraph) { foreach(var node in graph.GetNodes()) { node.GatherSRPCompatibility(ref srpSet); node.GatherSubTargetCompatibility(ref subTargetSet); } } GraphDataUtils.ApplyActionLeafFirst(graph, ValidateNode); } } } }