public bool Reload(HashSet <string> changedFileDependencies) { if (!changedFileDependencies.Contains(subGraphGuid)) { return(false); } if (asset == null) { // asset missing or deleted return(true); } if (changedFileDependencies.Contains(asset.assetGuid) || asset.descendents.Any(changedFileDependencies.Contains)) { m_SubGraph = null; UpdateSlots(); if (hasError) { return(true); } owner.ClearErrorsForNode(this); ValidateNode(); Dirty(ModificationScope.Graph); } return(true); }
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); }
void LoadSubGraph() { if (m_SubGraph == null) { if (string.IsNullOrEmpty(m_SerializedSubGraph)) { return; } var graphGuid = subGraphGuid; var assetPath = AssetDatabase.GUIDToAssetPath(graphGuid); if (string.IsNullOrEmpty(assetPath)) { // this happens if the editor has never seen the GUID // error will be printed by validation code in this case return; } m_SubGraph = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(assetPath); if (m_SubGraph == null) { // this happens if the editor has seen the GUID, but the file has been deleted since then // error will be printed by validation code in this case return; } m_SubGraph.LoadGraphData(); name = m_SubGraph.name; concretePrecision = m_SubGraph.outputPrecision; } }
public void Reload(HashSet <string> changedFileDependencies) { if (changedFileDependencies.Contains(asset.assetGuid) || asset.descendents.Any(changedFileDependencies.Contains)) { m_SubGraph = null; UpdateSlots(); owner.ClearErrorsForNode(this); ValidateNode(); Dirty(ModificationScope.Graph); } }
void LoadSubGraph() { if (m_SubGraph == null) { if (string.IsNullOrEmpty(m_SerializedSubGraph)) { return; } var graphGuid = subGraphGuid; var assetPath = AssetDatabase.GUIDToAssetPath(graphGuid); m_SubGraph = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(assetPath); if (m_SubGraph == null) { return; } name = m_SubGraph.name; concretePrecision = m_SubGraph.outputPrecision; } }
void LoadSubGraph() { if (m_SubGraph == null) { m_SubGraphData = null; if (string.IsNullOrEmpty(m_SerializedSubGraph)) { return; } var graphGuid = subGraphGuid; var assetPath = AssetDatabase.GUIDToAssetPath(graphGuid); m_SubGraph = AssetDatabase.LoadAssetAtPath <SubGraphAsset>(assetPath); if (m_SubGraph == null) { return; } name = m_SubGraph.name; var index = SubGraphDatabase.instance.subGraphGuids.BinarySearch(graphGuid); m_SubGraphData = index < 0 ? null : SubGraphDatabase.instance.subGraphs[index]; } }
static void ProcessSubGraph(SubGraphAsset asset, GraphData graph) { var registry = new FunctionRegistry(new ShaderStringBuilder(), true); registry.names.Clear(); asset.functions.Clear(); asset.isValid = true; graph.OnEnable(); graph.messageManager.ClearAll(); graph.ValidateGraph(); var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid); asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath)); asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}"; asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}"; asset.path = graph.path; var outputNode = graph.outputNode; var outputSlots = PooledList <MaterialSlot> .Get(); outputNode.GetInputSlots(outputSlots); List <AbstractMaterialNode> nodes = new List <AbstractMaterialNode>(); NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode); asset.effectiveShaderStage = ShaderStageCapability.All; foreach (var slot in outputSlots) { var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true); if (stage != ShaderStageCapability.All) { asset.effectiveShaderStage = stage; break; } } asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode as SubGraphOutputNode); asset.requirements = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false); asset.graphPrecision = graph.concretePrecision; asset.outputPrecision = outputNode.concretePrecision; asset.previewMode = graph.previewMode; GatherDescendentsFromGraph(new GUID(asset.assetGuid), out var containsCircularDependency, out var descendents); asset.descendents.AddRange(descendents.Select(g => g.ToString())); asset.descendents.Sort(); // ensure deterministic order var childrenSet = new HashSet <string>(); var anyErrors = false; foreach (var node in nodes) { if (node is SubGraphNode subGraphNode) { var subGraphGuid = subGraphNode.subGraphGuid; childrenSet.Add(subGraphGuid); } if (node.hasError) { anyErrors = true; } asset.children = childrenSet.ToList(); asset.children.Sort(); // ensure deterministic order } if (!anyErrors && containsCircularDependency) { Debug.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset); anyErrors = true; } if (anyErrors) { asset.isValid = false; registry.ProvideFunction(asset.functionName, sb => { }); return; } foreach (var node in nodes) { if (node is IGeneratesFunction generatesFunction) { registry.builder.currentNode = node; generatesFunction.GenerateNodeFunction(registry, GenerationMode.ForReals); registry.builder.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } // provide top level subgraph function registry.ProvideFunction(asset.functionName, sb => { GenerationUtils.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName); sb.AppendNewLine(); // Generate arguments... first INPUTS var arguments = new List <string>(); foreach (var prop in graph.properties) { prop.ValidateConcretePrecision(asset.graphPrecision); arguments.Add(prop.GetPropertyAsArgumentString()); } // now pass surface inputs arguments.Add(string.Format("{0} IN", asset.inputStructName)); // Now generate outputs foreach (MaterialSlot output in outputSlots) { arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputPrecision)} {output.shaderOutputName}_{output.id}"); } // Vt Feedback arguments foreach (var output in asset.vtFeedbackVariables) { arguments.Add($"out {ConcreteSlotValueType.Vector4.ToShaderString(ConcretePrecision.Single)} {output}_out"); } // Create the function prototype from the arguments sb.AppendLine("void {0}({1})" , asset.functionName , arguments.Aggregate((current, next) => $"{current}, {next}")); // now generate the function using (sb.BlockScope()) { // Just grab the body from the active nodes foreach (var node in nodes) { if (node is IGeneratesBodyCode generatesBodyCode) { sb.currentNode = node; generatesBodyCode.GenerateNodeCode(sb, GenerationMode.ForReals); sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } foreach (var slot in outputSlots) { sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals, asset.outputPrecision)};"); } foreach (var slot in asset.vtFeedbackVariables) { sb.AppendLine($"{slot}_out = {slot};"); } } }); asset.functions.AddRange(registry.names.Select(x => new FunctionPair(x, registry.sources[x].code))); var collector = new PropertyCollector(); foreach (var node in nodes) { int previousPropertyCount = Math.Max(0, collector.properties.Count - 1); node.CollectShaderProperties(collector, GenerationMode.ForReals); // This is a stop-gap to prevent the autogenerated values from JsonObject and ShaderInput from // resulting in non-deterministic import data. While we should move to local ids in the future, // this will prevent cascading shader recompilations. for (int i = previousPropertyCount; i < collector.properties.Count; ++i) { var prop = collector.properties[i]; var namespaceId = node.objectId; var nameId = prop.referenceName; prop.OverrideObjectId(namespaceId, nameId + "_ObjectId_" + i); prop.OverrideGuid(namespaceId, nameId + "_Guid_" + i); } } asset.WriteData(graph.properties, graph.keywords, collector.properties, outputSlots, graph.unsupportedTargets); outputSlots.Dispose(); }
static void ProcessSubGraph(SubGraphAsset asset, GraphData graph) { var graphIncludes = new IncludeCollection(); var registry = new FunctionRegistry(new ShaderStringBuilder(), graphIncludes, true); asset.functions.Clear(); asset.isValid = true; graph.OnEnable(); graph.messageManager.ClearAll(); graph.ValidateGraph(); var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid); asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath)); asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}_$precision"; asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}_$precision"; asset.path = graph.path; var outputNode = graph.outputNode; var outputSlots = PooledList <MaterialSlot> .Get(); outputNode.GetInputSlots(outputSlots); List <AbstractMaterialNode> nodes = new List <AbstractMaterialNode>(); NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode); asset.effectiveShaderStage = ShaderStageCapability.All; foreach (var slot in outputSlots) { var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true); if (stage != ShaderStageCapability.All) { asset.effectiveShaderStage = stage; break; } } asset.vtFeedbackVariables = VirtualTexturingFeedbackUtils.GetFeedbackVariables(outputNode as SubGraphOutputNode); asset.requirements = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false); // output precision is whatever the output node has as a graph precision, falling back to the graph default asset.outputGraphPrecision = outputNode.graphPrecision.GraphFallback(graph.graphDefaultPrecision); // this saves the graph precision, which indicates whether this subgraph is switchable or not asset.subGraphGraphPrecision = graph.graphDefaultPrecision; asset.previewMode = graph.previewMode; asset.includes = graphIncludes; GatherDescendentsFromGraph(new GUID(asset.assetGuid), out var containsCircularDependency, out var descendents); asset.descendents.AddRange(descendents.Select(g => g.ToString())); asset.descendents.Sort(); // ensure deterministic order var childrenSet = new HashSet <string>(); var anyErrors = false; foreach (var node in nodes) { if (node is SubGraphNode subGraphNode) { var subGraphGuid = subGraphNode.subGraphGuid; childrenSet.Add(subGraphGuid); } if (node.hasError) { anyErrors = true; } asset.children = childrenSet.ToList(); asset.children.Sort(); // ensure deterministic order } if (!anyErrors && containsCircularDependency) { Debug.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset); anyErrors = true; } if (anyErrors) { asset.isValid = false; registry.ProvideFunction(asset.functionName, sb => {}); return; } foreach (var node in nodes) { if (node is IGeneratesFunction generatesFunction) { registry.builder.currentNode = node; generatesFunction.GenerateNodeFunction(registry, GenerationMode.ForReals); } } // provide top level subgraph function // NOTE: actual concrete precision here shouldn't matter, it's irrelevant when building the subgraph asset registry.ProvideFunction(asset.functionName, asset.subGraphGraphPrecision, ConcretePrecision.Single, sb => { GenerationUtils.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName); sb.AppendNewLine(); // Generate the arguments... first INPUTS var arguments = new List <string>(); foreach (var prop in graph.properties) { // apply fallback to the graph default precision (but don't convert to concrete) // this means "graph switchable" properties will use the precision token GraphPrecision propGraphPrecision = prop.precision.ToGraphPrecision(graph.graphDefaultPrecision); string precisionString = propGraphPrecision.ToGenericString(); arguments.Add(prop.GetPropertyAsArgumentString(precisionString)); if (prop.isConnectionTestable) { arguments.Add($"bool {prop.GetConnectionStateHLSLVariableName()}"); } } { var dropdowns = graph.dropdowns; foreach (var dropdown in dropdowns) { arguments.Add($"int {dropdown.referenceName}"); } } // now pass surface inputs arguments.Add(string.Format("{0} IN", asset.inputStructName)); // Now generate output arguments foreach (MaterialSlot output in outputSlots) { arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputGraphPrecision.ToGenericString())} {output.shaderOutputName}_{output.id}"); } // Vt Feedback output arguments (always full float4) foreach (var output in asset.vtFeedbackVariables) { arguments.Add($"out {ConcreteSlotValueType.Vector4.ToShaderString(ConcretePrecision.Single)} {output}_out"); } // Create the function prototype from the arguments sb.AppendLine("void {0}({1})" , asset.functionName , arguments.Aggregate((current, next) => $"{current}, {next}")); // now generate the function using (sb.BlockScope()) { // Just grab the body from the active nodes foreach (var node in nodes) { if (node is IGeneratesBodyCode generatesBodyCode) { sb.currentNode = node; generatesBodyCode.GenerateNodeCode(sb, GenerationMode.ForReals); if (node.graphPrecision == GraphPrecision.Graph) { // code generated by nodes that use graph precision stays in generic form with embedded tokens // those tokens are replaced when this subgraph function is pulled into a graph that defines the precision } else { sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } } foreach (var slot in outputSlots) { sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals)};"); } foreach (var slot in asset.vtFeedbackVariables) { sb.AppendLine($"{slot}_out = {slot};"); } } }); // save all of the node-declared functions to the subgraph asset foreach (var name in registry.names) { var source = registry.sources[name]; var func = new FunctionPair(name, source.code, source.graphPrecisionFlags); asset.functions.Add(func); } var collector = new PropertyCollector(); foreach (var node in nodes) { int previousPropertyCount = Math.Max(0, collector.propertyCount - 1); node.CollectShaderProperties(collector, GenerationMode.ForReals); // This is a stop-gap to prevent the autogenerated values from JsonObject and ShaderInput from // resulting in non-deterministic import data. While we should move to local ids in the future, // this will prevent cascading shader recompilations. for (int i = previousPropertyCount; i < collector.propertyCount; ++i) { var prop = collector.GetProperty(i); var namespaceId = node.objectId; var nameId = prop.referenceName; prop.OverrideObjectId(namespaceId, nameId + "_ObjectId_" + i); prop.OverrideGuid(namespaceId, nameId + "_Guid_" + i); } } asset.WriteData(graph.properties, graph.keywords, graph.dropdowns, collector.properties, outputSlots, graph.unsupportedTargets); outputSlots.Dispose(); }
static void ProcessSubGraph(SubGraphAsset asset, GraphData graph) { var registry = new FunctionRegistry(new ShaderStringBuilder(), true); registry.names.Clear(); asset.functions.Clear(); asset.nodeProperties.Clear(); asset.isValid = true; graph.OnEnable(); graph.messageManager.ClearAll(); graph.ValidateGraph(); var assetPath = AssetDatabase.GUIDToAssetPath(asset.assetGuid); asset.hlslName = NodeUtils.GetHLSLSafeName(Path.GetFileNameWithoutExtension(assetPath)); asset.inputStructName = $"Bindings_{asset.hlslName}_{asset.assetGuid}"; asset.functionName = $"SG_{asset.hlslName}_{asset.assetGuid}"; asset.path = graph.path; var outputNode = (SubGraphOutputNode)graph.outputNode; asset.outputs.Clear(); outputNode.GetInputSlots(asset.outputs); List <AbstractMaterialNode> nodes = new List <AbstractMaterialNode>(); NodeUtils.DepthFirstCollectNodesFromNode(nodes, outputNode); asset.effectiveShaderStage = ShaderStageCapability.All; foreach (var slot in asset.outputs) { var stage = NodeUtils.GetEffectiveShaderStageCapability(slot, true); if (stage != ShaderStageCapability.All) { asset.effectiveShaderStage = stage; break; } } asset.requirements = ShaderGraphRequirements.FromNodes(nodes, asset.effectiveShaderStage, false); asset.inputs = graph.properties.ToList(); asset.graphPrecision = graph.concretePrecision; asset.outputPrecision = outputNode.concretePrecision; GatherFromGraph(assetPath, out var containsCircularDependency, out var descendents); asset.descendents.AddRange(descendents); var childrenSet = new HashSet <string>(); var anyErrors = false; foreach (var node in nodes) { if (node is SubGraphNode subGraphNode) { var subGraphGuid = subGraphNode.subGraphGuid; if (childrenSet.Add(subGraphGuid)) { asset.children.Add(subGraphGuid); } } if (node.hasError) { anyErrors = true; } } if (!anyErrors && containsCircularDependency) { Debug.LogError($"Error in Graph at {assetPath}: Sub Graph contains a circular dependency.", asset); anyErrors = true; } if (anyErrors) { asset.isValid = false; registry.ProvideFunction(asset.functionName, sb => { }); return; } foreach (var node in nodes) { if (node is IGeneratesFunction generatesFunction) { registry.builder.currentNode = node; generatesFunction.GenerateNodeFunction(registry, new GraphContext(asset.inputStructName), GenerationMode.ForReals); registry.builder.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } registry.ProvideFunction(asset.functionName, sb => { var graphContext = new GraphContext(asset.inputStructName); GraphUtil.GenerateSurfaceInputStruct(sb, asset.requirements, asset.inputStructName); sb.AppendNewLine(); // Generate arguments... first INPUTS var arguments = new List <string>(); foreach (var prop in asset.inputs) { prop.ValidateConcretePrecision(asset.graphPrecision); arguments.Add(string.Format("{0}", prop.GetPropertyAsArgumentString())); } // now pass surface inputs arguments.Add(string.Format("{0} IN", asset.inputStructName)); // Now generate outputs foreach (var output in asset.outputs) { arguments.Add($"out {output.concreteValueType.ToShaderString(asset.outputPrecision)} {output.shaderOutputName}_{output.id}"); } // Create the function prototype from the arguments sb.AppendLine("void {0}({1})" , asset.functionName , arguments.Aggregate((current, next) => $"{current}, {next}")); // now generate the function using (sb.BlockScope()) { // Just grab the body from the active nodes foreach (var node in nodes) { if (node is IGeneratesBodyCode generatesBodyCode) { sb.currentNode = node; generatesBodyCode.GenerateNodeCode(sb, graphContext, GenerationMode.ForReals); sb.ReplaceInCurrentMapping(PrecisionUtil.Token, node.concretePrecision.ToShaderString()); } } foreach (var slot in asset.outputs) { sb.AppendLine($"{slot.shaderOutputName}_{slot.id} = {outputNode.GetSlotValue(slot.id, GenerationMode.ForReals, asset.outputPrecision)};"); } } }); asset.functions.AddRange(registry.names.Select(x => new FunctionPair(x, registry.sources[x]))); var collector = new PropertyCollector(); asset.nodeProperties = collector.properties; foreach (var node in nodes) { node.CollectShaderProperties(collector, GenerationMode.ForReals); } asset.OnBeforeSerialize(); }