UnityGame/Library/PackageCache/com.unity.shadergraph/Editor/Data/Util/FunctionRegistry.cs

134 lines
5.7 KiB
C#
Raw Normal View History

2024-10-27 10:53:47 +03:00
using System;
using System.Collections.Generic;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
namespace UnityEditor.ShaderGraph
{
class FunctionSource
{
public string code;
public HashSet<AbstractMaterialNode> nodes;
public bool isGeneric;
public int graphPrecisionFlags; // Flags<GraphPrecision>
public int concretePrecisionFlags; // Flags<ConcretePrecision>
}
class FunctionRegistry
{
Dictionary<string, FunctionSource> m_Sources = new Dictionary<string, FunctionSource>();
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<string, FunctionSource> sources => m_Sources;
public void RequiresIncludes(IncludeCollection includes)
{
m_Includes.Add(includes);
}
public void RequiresIncludePath(string includePath, bool shouldIncludeWithPragmas = false)
{
m_Includes.Add(includePath, IncludeLocation.Graph, shouldIncludeWithPragmas);
}
// 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<string> names { get; } = new List<string>();
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);
}
}
public void ProvideFunction(string name, Action<ShaderStringBuilder> generator)
{
ProvideFunction(name, builder.currentNode.graphPrecision, builder.currentNode.concretePrecision, generator);
}
}
}