public static int GetKeywordPermutationCount(this GraphData graph) { // Gather all unique keywords from the Graph including Sub Graphs IEnumerable <ShaderKeyword> allKeywords = graph.keywords; var subGraphNodes = graph.GetNodes <SubGraphNode>(); foreach (SubGraphNode subGraphNode in subGraphNodes) { if (subGraphNode.asset == null) { continue; } allKeywords = allKeywords.Union(subGraphNode.asset.keywords); } allKeywords = allKeywords.Distinct(); // Get permutation count for all Keywords int permutationCount = 1; foreach (ShaderKeyword keyword in allKeywords) { if (keyword.keywordType == KeywordType.Boolean) { permutationCount *= 2; } else { permutationCount *= keyword.entries.Count; } } return(permutationCount); }
static void CollectInputCapabilities(SubGraphAsset asset, GraphData graph) { // Collect each input's capabilities. There can be multiple property nodes // contributing to the same input, so we cache these in a map while building var inputCapabilities = new Dictionary <string, SlotCapability>(); // Walk all property node output slots, computing and caching the capabilities for that slot var propertyNodes = graph.GetNodes <PropertyNode>(); foreach (var propertyNode in propertyNodes) { foreach (var slot in propertyNode.GetOutputSlots <MaterialSlot>()) { var slotName = slot.RawDisplayName(); SlotCapability capabilityInfo; if (!inputCapabilities.TryGetValue(slotName, out capabilityInfo)) { capabilityInfo = new SlotCapability(); capabilityInfo.slotName = slotName; inputCapabilities.Add(propertyNode.property.displayName, capabilityInfo); } capabilityInfo.capabilities &= NodeUtils.GetEffectiveShaderStageCapability(slot, false); } } asset.inputCapabilities.AddRange(inputCapabilities.Values); }
public static void ApplyActionLeafFirst(GraphData graph, Action <AbstractMaterialNode> action) { var temporaryMarks = PooledHashSet <string> .Get(); var permanentMarks = PooledHashSet <string> .Get(); var slots = ListPool <MaterialSlot> .Get(); // Make sure we process a node's children before the node itself. var stack = StackPool <AbstractMaterialNode> .Get(); foreach (var node in graph.GetNodes <AbstractMaterialNode>()) { stack.Push(node); } while (stack.Count > 0) { var node = stack.Pop(); if (permanentMarks.Contains(node.objectId)) { continue; } if (temporaryMarks.Contains(node.objectId)) { action.Invoke(node); permanentMarks.Add(node.objectId); } else { temporaryMarks.Add(node.objectId); stack.Push(node); node.GetInputSlots(slots); foreach (var inputSlot in slots) { var nodeEdges = graph.GetEdges(inputSlot.slotReference); foreach (var edge in nodeEdges) { var fromSocketRef = edge.outputSlot; var childNode = fromSocketRef.node; if (childNode != null) { stack.Push(childNode); } } } slots.Clear(); } } StackPool <AbstractMaterialNode> .Release(stack); ListPool <MaterialSlot> .Release(slots); temporaryMarks.Dispose(); permanentMarks.Dispose(); }
public static void ConcretizeProperties(GraphData graph) { var propertyNodes = graph.GetNodes <PropertyNode>().Where(n => !graph.m_Properties.Any(p => p.guid == n.propertyGuid)).ToArray(); foreach (var pNode in propertyNodes) { graph.ReplacePropertyNodeWithConcreteNodeNoValidate(pNode); } }
void Generate(GenerationMode mode, string name, AssetCollection assetCollection, Target[] targets) { m_Mode = mode; m_Name = name; m_Builder = new ShaderStringBuilder(); m_ConfiguredTextures = new List <PropertyCollector.TextureInfo>(); m_assetCollection = assetCollection; m_ActiveBlocks = m_GraphData.GetNodes <BlockNode>().ToList(); m_TemporaryBlocks = new List <BlockNode>(); m_Targets = targets; BuildShader(); }
internal static void SendShaderGraphEvent(string assetGuid, GraphData graph) { //The event shouldn't be able to report if this is disabled but if we know we're not going to report //Lets early out and not waste time gathering all the data if (!EditorAnalytics.enabled) { return; } if (!EnableAnalytics()) { return; } Dictionary <string, int> nodeTypeAndCount = new Dictionary <string, int>(); var nodes = graph.GetNodes <AbstractMaterialNode>(); int subgraphCount = 0; foreach (var materialNode in nodes) { string nType = materialNode.GetType().ToString().Split('.').Last(); if (nType == "SubGraphNode") { subgraphCount += 1; } if (!nodeTypeAndCount.ContainsKey(nType)) { nodeTypeAndCount[nType] = 1; } else { nodeTypeAndCount[nType] += 1; } } var jsonRepr = DictionaryToJson(nodeTypeAndCount); var data = new AnalyticsData() { nodes = jsonRepr, node_count = nodes.Count(), asset_guid = assetGuid, subgraph_count = subgraphCount }; EditorAnalytics.SendEventWithLimit(k_EventName, data); }
public Generator(GraphData graphData, AbstractMaterialNode outputNode, GenerationMode mode, string name, AssetCollection assetCollection) { m_GraphData = graphData; m_OutputNode = outputNode; m_Mode = mode; m_Name = name; m_Builder = new ShaderStringBuilder(); m_ConfiguredTextures = new List <PropertyCollector.TextureInfo>(); m_assetCollection = assetCollection; m_Blocks = graphData.GetNodes <BlockNode>().ToList(); GetTargetImplementations(); BuildShader(); }
static string GetShaderText(string path, out List <PropertyCollector.TextureInfo> configuredTextures, List <string> sourceAssetDependencyPaths, out GraphData graph) { graph = null; string shaderString = null; var shaderName = Path.GetFileNameWithoutExtension(path); try { var textGraph = File.ReadAllText(path, Encoding.UTF8); graph = JsonUtility.FromJson <GraphData>(textGraph); graph.messageManager = new MessageManager(); graph.assetGuid = AssetDatabase.AssetPathToGUID(path); graph.OnEnable(); graph.ValidateGraph(); if (!string.IsNullOrEmpty(graph.path)) { shaderName = graph.path + "/" + shaderName; } shaderString = ((IMasterNode)graph.outputNode).GetShader(GenerationMode.ForReals, shaderName, out configuredTextures, sourceAssetDependencyPaths); if (sourceAssetDependencyPaths != null) { foreach (var node in graph.GetNodes <AbstractMaterialNode>()) { node.GetSourceAssetDependencies(sourceAssetDependencyPaths); } } if (graph.messageManager.nodeMessagesChanged) { shaderString = null; } } catch (Exception) { configuredTextures = new List <PropertyCollector.TextureInfo>(); // ignored } return(shaderString ?? k_ErrorShader.Replace("Hidden/GraphErrorShader2", shaderName)); }
public void ReplaceWith(GraphData other) { if (other == null) { throw new ArgumentException("Can only replace with another AbstractMaterialGraph", "other"); } using (var removedPropertiesPooledObject = ListPool <Guid> .GetDisposable()) { var removedPropertyGuids = removedPropertiesPooledObject.value; foreach (var property in m_Properties) { removedPropertyGuids.Add(property.guid); } foreach (var propertyGuid in removedPropertyGuids) { RemoveShaderPropertyNoValidate(propertyGuid); } } foreach (var otherProperty in other.properties) { if (!properties.Any(p => p.guid == otherProperty.guid)) { AddShaderProperty(otherProperty); } } other.ValidateGraph(); ValidateGraph(); // Current tactic is to remove all nodes and edges and then re-add them, such that depending systems // will re-initialize with new references. using (var removedGroupsPooledObject = ListPool <GroupData> .GetDisposable()) { var removedGroupDatas = removedGroupsPooledObject.value; removedGroupDatas.AddRange(m_Groups); foreach (var groupData in removedGroupDatas) { RemoveGroupNoValidate(groupData); } } using (var pooledList = ListPool <IEdge> .GetDisposable()) { var removedNodeEdges = pooledList.value; removedNodeEdges.AddRange(m_Edges); foreach (var edge in removedNodeEdges) { RemoveEdgeNoValidate(edge); } } using (var removedNodesPooledObject = ListPool <Guid> .GetDisposable()) { var removedNodeGuids = removedNodesPooledObject.value; removedNodeGuids.AddRange(m_Nodes.Where(n => n != null).Select(n => n.guid)); foreach (var nodeGuid in removedNodeGuids) { RemoveNodeNoValidate(m_NodeDictionary[nodeGuid]); } } ValidateGraph(); foreach (GroupData groupData in other.groups) { AddGroup(groupData); } foreach (var node in other.GetNodes <AbstractMaterialNode>()) { AddNodeNoValidate(node); } foreach (var edge in other.edges) { ConnectNoValidate(edge.outputSlot, edge.inputSlot); } ValidateGraph(); }
public override void OnImportAsset(AssetImportContext ctx) { var currentTime = DateTime.Now.Ticks; if (ctx.assetPath != path) { ctx.LogImportError("The sgpostsubgraph extension may only be used internally by Shader Graph."); return; } if (SubGraphDatabase.instance == null) { SubGraphDatabase.instance = ScriptableObject.CreateInstance <SubGraphDatabase>(); } var database = SubGraphDatabase.instance; var allSubGraphGuids = AssetDatabase.FindAssets($"t:{nameof(SubGraphAsset)}").ToList(); allSubGraphGuids.Sort(); var subGraphMap = new Dictionary <string, SubGraphData>(); var graphDataMap = new Dictionary <string, GraphData>(); foreach (var subGraphData in database.subGraphs) { if (allSubGraphGuids.BinarySearch(subGraphData.assetGuid) >= 0) { subGraphMap.Add(subGraphData.assetGuid, subGraphData); } } var dirtySubGraphGuids = new List <string>(); foreach (var subGraphGuid in allSubGraphGuids) { var subGraphAsset = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(AssetDatabase.GUIDToAssetPath(subGraphGuid)); if (!subGraphMap.TryGetValue(subGraphGuid, out var subGraphData)) { subGraphData = new SubGraphData(); } if (subGraphAsset.importedAt > subGraphData.processedAt) { dirtySubGraphGuids.Add(subGraphGuid); subGraphData.Reset(); subGraphData.processedAt = currentTime; var subGraphPath = AssetDatabase.GUIDToAssetPath(subGraphGuid); var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8); var graphData = new GraphData { isSubGraph = true, assetGuid = subGraphGuid }; JsonUtility.FromJsonOverwrite(textGraph, graphData); subGraphData.children.AddRange(graphData.GetNodes <SubGraphNode>().Select(x => x.subGraphGuid).Distinct()); subGraphData.assetGuid = subGraphGuid; subGraphMap[subGraphGuid] = subGraphData; graphDataMap[subGraphGuid] = graphData; } else { subGraphData.ancestors.Clear(); subGraphData.descendents.Clear(); subGraphData.isRecursive = false; } } database.subGraphs.Clear(); database.subGraphs.AddRange(subGraphMap.Values); database.subGraphs.Sort((s1, s2) => s1.assetGuid.CompareTo(s2.assetGuid)); database.subGraphGuids.Clear(); database.subGraphGuids.AddRange(database.subGraphs.Select(x => x.assetGuid)); var permanentMarks = new HashSet <string>(); var stack = new Stack <string>(allSubGraphGuids.Count); // Detect recursion, and populate `ancestors` and `descendents` per sub graph. foreach (var rootSubGraphData in database.subGraphs) { var rootSubGraphGuid = rootSubGraphData.assetGuid; stack.Push(rootSubGraphGuid); while (stack.Count > 0) { var subGraphGuid = stack.Pop(); if (!permanentMarks.Add(subGraphGuid)) { continue; } var subGraphData = subGraphMap[subGraphGuid]; if (subGraphData != rootSubGraphData) { subGraphData.ancestors.Add(rootSubGraphGuid); rootSubGraphData.descendents.Add(subGraphGuid); } foreach (var childSubGraphGuid in subGraphData.children) { if (childSubGraphGuid == rootSubGraphGuid) { rootSubGraphData.isRecursive = true; } else if (subGraphMap.ContainsKey(childSubGraphGuid)) { stack.Push(childSubGraphGuid); } } } permanentMarks.Clear(); } // Next up we build a list of sub graphs to be processed, which will later be sorted topologically. var sortedSubGraphs = new List <SubGraphData>(); foreach (var subGraphGuid in dirtySubGraphGuids) { var subGraphData = subGraphMap[subGraphGuid]; if (permanentMarks.Add(subGraphGuid)) { sortedSubGraphs.Add(subGraphData); } // Note that we're traversing up the graph via ancestors rather than descendents, because all Sub Graphs using the current sub graph needs to be re-processed. foreach (var ancestorGuid in subGraphData.ancestors) { if (permanentMarks.Add(ancestorGuid)) { var ancestorSubGraphData = subGraphMap[ancestorGuid]; sortedSubGraphs.Add(ancestorSubGraphData); } } } permanentMarks.Clear(); // Sort topologically. At this stage we can assume there are no loops because all recursive sub graphs have been filtered out. sortedSubGraphs.Sort((s1, s2) => s1.descendents.Contains(s2.assetGuid) ? 1 : s2.descendents.Contains(s1.assetGuid) ? -1 : 0); // Finally process the topologically sorted sub graphs without recursion. var registry = new FunctionRegistry(new ShaderStringBuilder(), true); var messageManager = new MessageManager(); foreach (var subGraphData in sortedSubGraphs) { try { var subGraphPath = AssetDatabase.GUIDToAssetPath(subGraphData.assetGuid); if (!graphDataMap.TryGetValue(subGraphData.assetGuid, out var graphData)) { var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8); graphData = new GraphData { isSubGraph = true, assetGuid = subGraphData.assetGuid }; JsonUtility.FromJsonOverwrite(textGraph, graphData); } graphData.messageManager = messageManager; ProcessSubGraph(subGraphMap, registry, subGraphData, graphData); if (messageManager.nodeMessagesChanged) { var subGraphAsset = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(AssetDatabase.GUIDToAssetPath(subGraphData.assetGuid)); foreach (var pair in messageManager.GetNodeMessages()) { var node = graphData.GetNodeFromTempId(pair.Key); foreach (var message in pair.Value) { MessageManager.Log(node, subGraphPath, message, subGraphAsset); } } } } catch (Exception e) { subGraphData.isValid = false; var subGraphAsset = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(AssetDatabase.GUIDToAssetPath(subGraphData.assetGuid)); Debug.LogException(e, subGraphAsset); } finally { subGraphData.processedAt = currentTime; messageManager.ClearAll(); } } // Carry over functions used by sub-graphs that were not re-processed in this import. foreach (var subGraphData in database.subGraphs) { foreach (var functionName in subGraphData.functionNames) { if (!registry.sources.ContainsKey(functionName)) { registry.sources.Add(functionName, database.functionSources[database.functionNames.BinarySearch(functionName)]); } } } var functions = registry.sources.ToList(); functions.Sort((p1, p2) => p1.Key.CompareTo(p2.Key)); database.functionNames.Clear(); database.functionSources.Clear(); foreach (var pair in functions) { database.functionNames.Add(pair.Key); database.functionSources.Add(pair.Value); } ctx.AddObjectToAsset("MainAsset", database); ctx.SetMainObject(database); SubGraphDatabase.instance = null; }