using System; using System.Collections.Generic; using UnityEditor.ShaderGraph.Internal; using UnityEngine; namespace UnityEditor.ShaderGraph { class FunctionSource { public string code; public HashSet nodes; public bool isGeneric; public int graphPrecisionFlags; // Flags public int concretePrecisionFlags; // Flags } class FunctionRegistry { Dictionary m_Sources = new Dictionary(); bool m_Validate = false; ShaderStringBuilder m_Builder; IncludeCollection m_Includes; public FunctionRegistry(ShaderStringBuilder builder, IncludeCollection includes, bool validate = false) { m_Builder = builder; m_Includes = includes; m_Validate = validate; } internal ShaderStringBuilder builder => m_Builder; public Dictionary sources => m_Sources; public void RequiresIncludes(IncludeCollection includes) { m_Includes.Add(includes); } public void RequiresIncludePath(string includePath) { m_Includes.Add(includePath, IncludeLocation.Graph); } // this list is somewhat redundant, but it preserves function declaration ordering // (i.e. when nodes add multiple functions, they require being defined in a certain order) public List names { get; } = new List(); public void ProvideFunction(string name, GraphPrecision graphPrecision, ConcretePrecision concretePrecision, Action 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; // trim fixes mismatched whitespace issue when nodes come from subgraphs var code = builder.ToString(startIndex, length).Trim(); // 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 { 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); } } public void ProvideFunction(string name, Action generator) { ProvideFunction(name, builder.currentNode.graphPrecision, builder.currentNode.concretePrecision, generator); } } }