public void ProvideFunction(string name, GraphPrecision graphPrecision, ConcretePrecision concretePrecision, Action <ShaderStringBuilder> generator) { // appends code, construct the standalone code string var originalIndex = builder.length; builder.AppendNewLine(); var startIndex = builder.length; generator(builder); var length = builder.length - startIndex; var code = builder.ToString(startIndex, length); // validate some assumptions around generics bool isGenericName = name.Contains("$"); bool isGenericFunc = code.Contains("$"); bool isGeneric = isGenericName || isGenericFunc; bool containsFunctionName = code.Contains(name); var curNode = builder.currentNode; if (isGenericName != isGenericFunc) { curNode.owner.AddValidationError(curNode.objectId, $"Function {name} provided by node {curNode.name} contains $precision tokens in the name or the code, but not both. This is very likely an error."); } if (!containsFunctionName) { curNode.owner.AddValidationError(curNode.objectId, $"Function {name} provided by node {curNode.name} does not contain the name of the function. This is very likely an error."); } int graphPrecisionFlag = (1 << (int)graphPrecision); int concretePrecisionFlag = (1 << (int)concretePrecision); FunctionSource existingSource; if (m_Sources.TryGetValue(name, out existingSource)) { // function already provided existingSource.nodes.Add(builder.currentNode); // let's check if the requested precision variant has already been provided (or if it's not generic there are no variants) bool concretePrecisionExists = ((existingSource.concretePrecisionFlags & concretePrecisionFlag) != 0) || !isGeneric; // if this precision was already added -- remove the duplicate code from the builder if (concretePrecisionExists) { builder.length -= (builder.length - originalIndex); } // save the flags existingSource.graphPrecisionFlags = existingSource.graphPrecisionFlags | graphPrecisionFlag; existingSource.concretePrecisionFlags = existingSource.concretePrecisionFlags | concretePrecisionFlag; // if validate, we double check that the two function declarations are the same if (m_Validate) { if (code != existingSource.code) { var errorMessage = string.Format("Function `{0}` has conflicting implementations:{1}{1}{2}{1}{1}{3}", name, Environment.NewLine, code, existingSource.code); foreach (var n in existingSource.nodes) { n.owner.AddValidationError(n.objectId, errorMessage); } } } } else { var newSource = new FunctionSource { code = code, isGeneric = isGeneric, graphPrecisionFlags = graphPrecisionFlag, concretePrecisionFlags = concretePrecisionFlag, nodes = new HashSet <AbstractMaterialNode> { builder.currentNode } }; m_Sources.Add(name, newSource); names.Add(name); } // fully concretize any generic code by replacing any precision tokens by the node's concrete precision if (isGeneric && (builder.length > originalIndex)) { int start = originalIndex; int count = builder.length - start; builder.Replace(PrecisionUtil.Token, concretePrecision.ToShaderString(), start, count); } }
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)); } // 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.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(); }
public TestCase(GraphPrecision graphPrecision, Precision parentPrecision0, Precision parentPrecision1, GraphPrecision resultGraph, ConcretePrecision resultConcrete) { this.graphPrecision = graphPrecision; this.parentPrecision0 = parentPrecision0; this.parentPrecision1 = parentPrecision1; this.resultGraph = resultGraph; this.resultConcrete = resultConcrete; }