A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using UnityEditor.ShaderGraph.Internal;
4using UnityEngine;
5
6namespace UnityEditor.ShaderGraph
7{
8 class FunctionSource
9 {
10 public string code;
11 public HashSet<AbstractMaterialNode> nodes;
12 public bool isGeneric;
13 public int graphPrecisionFlags; // Flags<GraphPrecision>
14 public int concretePrecisionFlags; // Flags<ConcretePrecision>
15 }
16
17 class FunctionRegistry
18 {
19 Dictionary<string, FunctionSource> m_Sources = new Dictionary<string, FunctionSource>();
20 bool m_Validate = false;
21 ShaderStringBuilder m_Builder;
22 IncludeCollection m_Includes;
23
24 public FunctionRegistry(ShaderStringBuilder builder, IncludeCollection includes, bool validate = false)
25 {
26 m_Builder = builder;
27 m_Includes = includes;
28 m_Validate = validate;
29 }
30
31 internal ShaderStringBuilder builder => m_Builder;
32
33 public Dictionary<string, FunctionSource> sources => m_Sources;
34
35 public void RequiresIncludes(IncludeCollection includes)
36 {
37 m_Includes.Add(includes);
38 }
39
40 public void RequiresIncludePath(string includePath, bool shouldIncludeWithPragmas = false)
41 {
42 m_Includes.Add(includePath, IncludeLocation.Graph, shouldIncludeWithPragmas);
43 }
44
45 // this list is somewhat redundant, but it preserves function declaration ordering
46 // (i.e. when nodes add multiple functions, they require being defined in a certain order)
47 public List<string> names { get; } = new List<string>();
48
49 public void ProvideFunction(string name, GraphPrecision graphPrecision, ConcretePrecision concretePrecision, Action<ShaderStringBuilder> generator)
50 {
51 // appends code, construct the standalone code string
52 var originalIndex = builder.length;
53 builder.AppendNewLine();
54
55 var startIndex = builder.length;
56 generator(builder);
57 var length = builder.length - startIndex;
58 var code = builder.ToString(startIndex, length);
59
60 // validate some assumptions around generics
61 bool isGenericName = name.Contains("$");
62 bool isGenericFunc = code.Contains("$");
63 bool isGeneric = isGenericName || isGenericFunc;
64 bool containsFunctionName = code.Contains(name);
65
66 var curNode = builder.currentNode;
67 if (isGenericName != isGenericFunc)
68 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.");
69
70 if (!containsFunctionName)
71 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.");
72
73 int graphPrecisionFlag = (1 << (int)graphPrecision);
74 int concretePrecisionFlag = (1 << (int)concretePrecision);
75
76 FunctionSource existingSource;
77 if (m_Sources.TryGetValue(name, out existingSource))
78 {
79 // function already provided
80 existingSource.nodes.Add(builder.currentNode);
81
82 // let's check if the requested precision variant has already been provided (or if it's not generic there are no variants)
83 bool concretePrecisionExists = ((existingSource.concretePrecisionFlags & concretePrecisionFlag) != 0) || !isGeneric;
84
85 // if this precision was already added -- remove the duplicate code from the builder
86 if (concretePrecisionExists)
87 builder.length -= (builder.length - originalIndex);
88
89 // save the flags
90 existingSource.graphPrecisionFlags = existingSource.graphPrecisionFlags | graphPrecisionFlag;
91 existingSource.concretePrecisionFlags = existingSource.concretePrecisionFlags | concretePrecisionFlag;
92
93 // if validate, we double check that the two function declarations are the same
94 if (m_Validate)
95 {
96 if (code != existingSource.code)
97 {
98 var errorMessage = string.Format("Function `{0}` has conflicting implementations:{1}{1}{2}{1}{1}{3}", name, Environment.NewLine, code, existingSource.code);
99 foreach (var n in existingSource.nodes)
100 n.owner.AddValidationError(n.objectId, errorMessage);
101 }
102 }
103 }
104 else
105 {
106 var newSource = new FunctionSource
107 {
108 code = code,
109 isGeneric = isGeneric,
110 graphPrecisionFlags = graphPrecisionFlag,
111 concretePrecisionFlags = concretePrecisionFlag,
112 nodes = new HashSet<AbstractMaterialNode> { builder.currentNode }
113 };
114
115 m_Sources.Add(name, newSource);
116 names.Add(name);
117 }
118
119 // fully concretize any generic code by replacing any precision tokens by the node's concrete precision
120 if (isGeneric && (builder.length > originalIndex))
121 {
122 int start = originalIndex;
123 int count = builder.length - start;
124 builder.Replace(PrecisionUtil.Token, concretePrecision.ToShaderString(), start, count);
125 }
126 }
127
128 public void ProvideFunction(string name, Action<ShaderStringBuilder> generator)
129 {
130 ProvideFunction(name, builder.currentNode.graphPrecision, builder.currentNode.concretePrecision, generator);
131 }
132 }
133}