Boat Attack使用了Universal RP的许多新图形功能,可以用于探索 Universal RP 的使用方式和技巧。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

933 行
38 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Data.Util;
namespace UnityEditor.ShaderGraph
{
static class ShaderSpliceUtil
{
enum BaseFieldType
{
Invalid,
Float,
Uint,
};
private static BaseFieldType GetBaseFieldType(string typeName)
{
if (typeName.StartsWith("Vector") || typeName.Equals("Single"))
{
return BaseFieldType.Float;
}
if (typeName.StartsWith("UInt32")) // We don't have proper support for uint (Uint, Uint2, Uint3, Uint4). Need these types, for now just supporting instancing via a single uint.
{
return BaseFieldType.Uint;
}
return BaseFieldType.Invalid;
}
private static int GetComponentCount(string typeName)
{
switch (GetBaseFieldType(typeName))
{
case BaseFieldType.Float:
return GetFloatVectorCount(typeName);
case BaseFieldType.Uint:
return GetUintCount(typeName);
default:
return 0;
}
}
private static int GetFloatVectorCount(string typeName)
{
if (typeName.Equals("Vector4"))
{
return 4;
}
else if (typeName.Equals("Vector3"))
{
return 3;
}
else if (typeName.Equals("Vector2"))
{
return 2;
}
else if (typeName.Equals("Single"))
{
return 1;
}
else
{
return 0;
}
}
// Need uint types
private static int GetUintCount(string typeName)
{
if (typeName.Equals("UInt32"))
{
return 1;
}
else
{
return 0;
}
}
private static string[] vectorTypeNames =
{
"unknown",
"float",
"float2",
"float3",
"float4"
};
private static string[] uintTypeNames =
{
"unknown",
"uint",
};
private static char[] channelNames =
{ 'x', 'y', 'z', 'w' };
private static string GetChannelSwizzle(int firstChannel, int channelCount)
{
System.Text.StringBuilder result = new System.Text.StringBuilder();
int lastChannel = System.Math.Min(firstChannel + channelCount - 1, 4);
for (int index = firstChannel; index <= lastChannel; index++)
{
result.Append(channelNames[index]);
}
return result.ToString();
}
private static bool ShouldSpliceField(System.Type parentType, FieldInfo field, IActiveFields activeFields, out bool isOptional)
{
bool fieldActive = true;
isOptional = field.IsDefined(typeof(Optional), false);
if (isOptional)
{
string fullName = parentType.Name + "." + field.Name;
if (!activeFields.Contains(fullName))
{
// not active, skip the optional field
fieldActive = false;
}
}
return fieldActive;
}
private static string GetFieldSemantic(FieldInfo field)
{
string semanticString = null;
object[] semantics = field.GetCustomAttributes(typeof(Semantic), false);
if (semantics.Length > 0)
{
Semantic firstSemantic = (Semantic)semantics[0];
semanticString = " : " + firstSemantic.semantic;
}
return semanticString;
}
private static string GetFieldType(FieldInfo field, out int componentCount)
{
string fieldType;
object[] overrideType = field.GetCustomAttributes(typeof(OverrideType), false);
if (overrideType.Length > 0)
{
OverrideType first = (OverrideType)overrideType[0];
fieldType = first.typeName;
componentCount = 0;
}
else
{
// TODO: handle non-float types
componentCount = GetComponentCount(field.FieldType.Name);
switch (GetBaseFieldType(field.FieldType.Name))
{
case BaseFieldType.Float:
fieldType = vectorTypeNames[componentCount];
break;
case BaseFieldType.Uint:
fieldType = uintTypeNames[componentCount];
break;
default:
fieldType = "unknown";
break;
}
}
return fieldType;
}
private static bool IsFloatVectorType(string type)
{
return GetFloatVectorCount(type) != 0;
}
private static string GetFieldConditional(FieldInfo field)
{
string conditional = null;
object[] overrideType = field.GetCustomAttributes(typeof(PreprocessorIf), false);
if (overrideType.Length > 0)
{
PreprocessorIf first = (PreprocessorIf)overrideType[0];
conditional = first.conditional;
}
return conditional;
}
public static void BuildType(System.Type t, ActiveFields activeFields, ShaderGenerator result, bool isDebug)
{
result.AddShaderChunk("struct " + t.Name);
result.AddShaderChunk("{");
result.Indent();
foreach (FieldInfo field in t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{
if (field.MemberType == MemberTypes.Field)
{
bool isOptional = false;
var fieldIsActive = false;
var keywordIfdefs = string.Empty;
if (activeFields.permutationCount > 0)
{
// Evaluate all activeFields instance
var instances = activeFields
.allPermutations.instances
.Where(i => ShouldSpliceField(t, field, i, out isOptional))
.ToList();
fieldIsActive = instances.Count > 0;
if (fieldIsActive)
keywordIfdefs = KeywordUtil.GetKeywordPermutationSetConditional(instances
.Select(i => i.permutationIndex).ToList());
}
else
{
fieldIsActive = ShouldSpliceField(t, field, activeFields.baseInstance, out isOptional);
}
if (fieldIsActive)
{
// The field is used, so generate it
var semanticString = GetFieldSemantic(field);
int componentCount;
var fieldType = GetFieldType(field, out componentCount);
var conditional = GetFieldConditional(field);
if (conditional != null)
result.AddShaderChunk("#if " + conditional);
if (!string.IsNullOrEmpty(keywordIfdefs))
result.AddShaderChunk(keywordIfdefs);
var fieldDecl = fieldType + " " + field.Name + semanticString + ";" + (isOptional & isDebug ? " // optional" : string.Empty);
result.AddShaderChunk(fieldDecl);
if (!string.IsNullOrEmpty(keywordIfdefs))
result.AddShaderChunk("#endif" + (isDebug ? " // Shader Graph Keywords" : string.Empty));
if (conditional != null)
result.AddShaderChunk("#endif" + (isDebug ? $" // {conditional}" : string.Empty));
}
}
}
result.Deindent();
result.AddShaderChunk("};");
object[] packAttributes = t.GetCustomAttributes(typeof(InterpolatorPack), false);
if (packAttributes.Length > 0)
{
result.AddNewLine();
if (activeFields.permutationCount > 0)
{
var generatedPackedTypes = new Dictionary<string, (ShaderGenerator, List<int>)>();
foreach (var instance in activeFields.allPermutations.instances)
{
var instanceGenerator = new ShaderGenerator();
BuildPackedType(t, instance, instanceGenerator, isDebug);
var key = instanceGenerator.GetShaderString(0);
if (generatedPackedTypes.TryGetValue(key, out var value))
value.Item2.Add(instance.permutationIndex);
else
generatedPackedTypes.Add(key, (instanceGenerator, new List<int> { instance.permutationIndex }));
}
var isFirst = true;
foreach (var generated in generatedPackedTypes)
{
if (isFirst)
{
isFirst = false;
result.AddShaderChunk(KeywordUtil.GetKeywordPermutationSetConditional(generated.Value.Item2));
}
else
result.AddShaderChunk(KeywordUtil.GetKeywordPermutationSetConditional(generated.Value.Item2).Replace("#if", "#elif"));
result.AddGenerator(generated.Value.Item1);
}
if (generatedPackedTypes.Count > 0)
result.AddShaderChunk("#endif");
}
else
{
BuildPackedType(t, activeFields.baseInstance, result, isDebug);
}
}
}
public static void BuildPackedType(System.Type unpacked, IActiveFields activeFields, ShaderGenerator result, bool isDebug)
{
// for each interpolator, the number of components used (up to 4 for a float4 interpolator)
List<int> packedCounts = new List<int>();
ShaderGenerator packingStruct = new ShaderGenerator();
ShaderGenerator packer = new ShaderGenerator();
ShaderGenerator unpacker = new ShaderGenerator();
ShaderGenerator systemGenerated = new ShaderGenerator();
string unpackedStruct = unpacked.Name.ToString();
string packedStruct = "Packed" + unpacked.Name;
string packerFunction = "Pack" + unpacked.Name;
string unpackerFunction = "Unpack" + unpacked.Name;
// declare struct header:
// struct packedStruct {
packingStruct.AddShaderChunk("// Generated Type: Packed" + unpacked.Name);
packingStruct.AddShaderChunk("struct " + packedStruct);
packingStruct.AddShaderChunk("{");
packingStruct.Indent();
// declare function headers:
// packedStruct packerFunction(unpackedStruct input)
// {
// packedStruct output;
packer.AddShaderChunk("// Packed Type: " + unpacked.Name);
packer.AddShaderChunk(packedStruct + " " + packerFunction + "(" + unpackedStruct + " input)");
packer.AddShaderChunk("{");
packer.Indent();
packer.AddShaderChunk(packedStruct + " output;");
// unpackedStruct unpackerFunction(packedStruct input)
// {
// unpackedStruct output;
unpacker.AddShaderChunk("// Unpacked Type: " + unpacked.Name);
unpacker.AddShaderChunk(unpackedStruct + " " + unpackerFunction + "(" + packedStruct + " input)");
unpacker.AddShaderChunk("{");
unpacker.Indent();
unpacker.AddShaderChunk(unpackedStruct + " output;");
// TODO: this could do a better job packing
// especially if we allowed breaking up fields across multiple interpolators (to pack them into remaining space...)
// though we would want to only do this if it improves final interpolator count, and is worth it on the target machine
foreach (FieldInfo field in unpacked.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{
if (field.MemberType == MemberTypes.Field)
{
bool isOptional;
if (ShouldSpliceField(unpacked, field, activeFields, out isOptional))
{
string semanticString = GetFieldSemantic(field);
int floatVectorCount;
string fieldType = GetFieldType(field, out floatVectorCount);
string conditional = GetFieldConditional(field);
// System generated fields must appear last in struct definitions
bool isSystemGenerated = field.IsDefined(typeof(SystemGenerated), false);
if (conditional != null)
{
if(isSystemGenerated)
{
systemGenerated.AddShaderChunk("#if " + conditional);
}
else
{
packingStruct.AddShaderChunk("#if " + conditional);
}
packer.AddShaderChunk("#if " + conditional);
unpacker.AddShaderChunk("#if " + conditional);
}
if ((semanticString != null) || (floatVectorCount == 0))
{
if(isSystemGenerated)
{
systemGenerated.AddShaderChunk(fieldType + " " + field.Name + semanticString + ";" + (isDebug ? " // unpacked" : string.Empty));
}
else
{
packingStruct.AddShaderChunk(fieldType + " " + field.Name + semanticString + ";" + (isDebug ? " // unpacked" : string.Empty));
}
packer.AddShaderChunk("output." + field.Name + " = input." + field.Name + ";");
unpacker.AddShaderChunk("output." + field.Name + " = input." + field.Name + ";");
}
else
{
// pack float field
// super simple packing: use the first interpolator that has room for the whole value
int interpIndex = packedCounts.FindIndex(x => (x + floatVectorCount <= 4));
int firstChannel;
if (interpIndex < 0)
{
// allocate a new interpolator
interpIndex = packedCounts.Count;
firstChannel = 0;
packedCounts.Add(floatVectorCount);
}
else
{
// pack into existing interpolator
firstChannel = packedCounts[interpIndex];
packedCounts[interpIndex] += floatVectorCount;
}
// add code to packer and unpacker -- packed data declaration is handled later
string packedChannels = GetChannelSwizzle(firstChannel, floatVectorCount);
packer.AddShaderChunk(string.Format("output.interp{0:00}.{1} = input.{2};", interpIndex, packedChannels, field.Name));
unpacker.AddShaderChunk(string.Format("output.{0} = input.interp{1:00}.{2};", field.Name, interpIndex, packedChannels));
}
if (conditional != null)
{
if(isSystemGenerated)
{
systemGenerated.AddShaderChunk("#endif" + (isDebug ? $" // conditional" : string.Empty));
}
else
{
packingStruct.AddShaderChunk("#endif" + (isDebug ? $" // conditional" : string.Empty));
}
packer.AddShaderChunk("#endif" + (isDebug ? $" // conditional" : string.Empty));
unpacker.AddShaderChunk("#endif" + (isDebug ? $" // conditional" : string.Empty));
}
}
}
}
// add packed data declarations to struct, using the packedCounts
for (int index = 0; index < packedCounts.Count; index++)
{
int count = packedCounts[index];
packingStruct.AddShaderChunk(string.Format("{0} interp{1:00} : TEXCOORD{1};" + (isDebug ? " // auto-packed" : string.Empty), vectorTypeNames[count], index));
}
// Add system generated fields at the end
if(systemGenerated.numberOfChunks > 0)
{
packingStruct.AddGenerator(systemGenerated);
}
// close declarations
packingStruct.Deindent();
packingStruct.AddShaderChunk("};");
packingStruct.AddNewLine();
packer.AddShaderChunk("return output;");
packer.Deindent();
packer.AddShaderChunk("}");
packer.AddNewLine();
unpacker.AddShaderChunk("return output;");
unpacker.Deindent();
unpacker.AddShaderChunk("}");
// combine all of the code into the result
result.AddGenerator(packingStruct);
result.AddGenerator(packer);
result.AddGenerator(unpacker);
}
// returns the offset of the first non-whitespace character, in the range [start, end] inclusive ... will return end if none found
private static int SkipWhitespace(string str, int start, int end)
{
int index = start;
while (index < end)
{
char c = str[index];
if (!Char.IsWhiteSpace(c))
{
break;
}
index++;
}
return index;
}
public class TemplatePreprocessor
{
// inputs
ActiveFields activeFields;
Dictionary<string, string> namedFragments;
string templatePath;
bool isDebug;
string assemblyName;
string resourceClassName;
// intermediates
HashSet<string> includedFiles;
// outputs
ShaderStringBuilder result;
List<string> sourceAssetDependencyPaths;
public TemplatePreprocessor(ActiveFields activeFields, Dictionary<string, string> namedFragments, bool isDebug, string templatePath, List<string> sourceAssetDependencyPaths, string assemblyName, string resourceClassName, ShaderStringBuilder outShaderCodeResult = null)
{
this.activeFields = activeFields;
this.namedFragments = namedFragments;
this.isDebug = isDebug;
this.templatePath = templatePath;
this.sourceAssetDependencyPaths = sourceAssetDependencyPaths;
this.assemblyName = assemblyName;
this.resourceClassName = resourceClassName;
this.result = outShaderCodeResult ?? new ShaderStringBuilder();
includedFiles = new HashSet<string>();
}
public ShaderStringBuilder GetShaderCode()
{
return result;
}
public void ProcessTemplateFile(string filePath)
{
if (File.Exists(filePath) &&
!includedFiles.Contains(filePath))
{
includedFiles.Add(filePath);
if (sourceAssetDependencyPaths != null)
sourceAssetDependencyPaths.Add(filePath);
string[] templateLines = File.ReadAllLines(filePath);
foreach (string line in templateLines)
{
ProcessTemplateLine(line, 0, line.Length);
}
}
}
private struct Token
{
public string s;
public int start;
public int end;
public Token(string s, int start, int end)
{
this.s = s;
this.start = start;
this.end = end;
}
public static Token Invalid()
{
return new Token(null, 0, 0);
}
public bool IsValid()
{
return (s != null);
}
public bool Is(string other)
{
int len = end - start;
return (other.Length == len) && (0 == string.Compare(s, start, other, 0, len));
}
public string GetString()
{
int len = end - start;
if (len > 0)
{
return s.Substring(start, end - start);
}
return null;
}
}
public void ProcessTemplateLine(string line, int start, int end)
{
bool appendEndln = true;
int cur = start;
while (cur < end)
{
// find an escape code '$'
int dollar = line.IndexOf('$', cur, end - cur);
if (dollar < 0)
{
// no escape code found in the remaining code -- just append the rest verbatim
AppendSubstring(line, cur, true, end, false);
break;
}
else
{
// found $ escape sequence
Token command = ParseIdentifier(line, dollar+1, end);
if (!command.IsValid())
{
Error("ERROR: $ must be followed by a command string (if, splice, or include)", line, dollar+1);
break;
}
else
{
if (command.Is("include"))
{
ProcessIncludeCommand(command, end);
appendEndln = false;
break; // include command always ignores the rest of the line, error or not
}
else if (command.Is("splice"))
{
if (!ProcessSpliceCommand(command, end, ref cur))
{
// error, skip the rest of the line
break;
}
}
else if (command.Is("buildType"))
{
ProcessBuildTypeCommand(command, end);
appendEndln = false;
break; // buildType command always ignores the rest of the line, error or not
}
else
{
// let's see if it is a predicate
Token predicate = ParseUntil(line, dollar + 1, end, ':');
if (!predicate.IsValid())
{
Error("ERROR: unrecognized command: " + command.GetString(), line, command.start);
break;
}
else
{
if (!ProcessPredicate(predicate, end, ref cur, ref appendEndln))
{
break; // skip the rest of the line
}
}
}
}
}
}
if (appendEndln)
{
result.AppendNewLine();
}
}
private void ProcessIncludeCommand(Token includeCommand, int lineEnd)
{
if (Expect(includeCommand.s, includeCommand.end, '('))
{
Token param = ParseString(includeCommand.s, includeCommand.end + 1, lineEnd);
if (!param.IsValid())
{
Error("ERROR: $include expected a string file path parameter", includeCommand.s, includeCommand.end + 1);
}
else
{
var includeLocation = Path.Combine(templatePath, param.GetString());
if (!File.Exists(includeLocation))
{
Error("ERROR: $include cannot find file : " + includeLocation, includeCommand.s, param.start);
}
else
{
int endIndex = result.length;
using(var temp = new ShaderStringBuilder())
{
// Wrap in debug mode
if(isDebug)
{
result.AppendLine("//-------------------------------------------------------------------------------------");
result.AppendLine("// TEMPLATE INCLUDE : " + param.GetString());
result.AppendLine("//-------------------------------------------------------------------------------------");
result.AppendNewLine();
}
// Recursively process templates
ProcessTemplateFile(includeLocation);
// Wrap in debug mode
if(isDebug)
{
result.AppendNewLine();
result.AppendLine("//-------------------------------------------------------------------------------------");
result.AppendLine("// END TEMPLATE INCLUDE : " + param.GetString());
result.AppendLine("//-------------------------------------------------------------------------------------");
}
result.AppendNewLine();
// Required to enforce indentation rules
// Append lines from this include into temporary StringBuilder
// Reduce result length to remove this include
temp.AppendLines(result.ToString(endIndex, result.length - endIndex));
result.length = endIndex;
result.AppendLines(temp.ToCodeBlack());
}
}
}
}
}
private bool ProcessSpliceCommand(Token spliceCommand, int lineEnd, ref int cur)
{
if (!Expect(spliceCommand.s, spliceCommand.end, '('))
{
return false;
}
else
{
Token param = ParseUntil(spliceCommand.s, spliceCommand.end + 1, lineEnd, ')');
if (!param.IsValid())
{
Error("ERROR: splice command is missing a ')'", spliceCommand.s, spliceCommand.start);
return false;
}
else
{
// append everything before the beginning of the escape sequence
AppendSubstring(spliceCommand.s, cur, true, spliceCommand.start-1, false);
// find the named fragment
string name = param.GetString(); // unfortunately this allocates a new string
string fragment;
if ((namedFragments != null) && namedFragments.TryGetValue(name, out fragment))
{
// splice the fragment
result.Append(fragment);
}
else
{
// no named fragment found
result.Append("/* WARNING: $splice Could not find named fragment '{0}' */", name);
}
// advance to just after the ')' and continue parsing
cur = param.end + 1;
}
}
return true;
}
private void ProcessBuildTypeCommand(Token command, int endLine)
{
if (Expect(command.s, command.end, '('))
{
Token param = ParseUntil(command.s, command.end + 1, endLine, ')');
if (!param.IsValid())
{
Error("ERROR: buildType command is missing a ')'", command.s, command.start);
}
else
{
string typeName = param.GetString();
Type type = GenerationUtils.GetTypeForStruct(typeName, resourceClassName, assemblyName);
if (type == null)
{
Error("ERROR: buildType could not find type : " + typeName, command.s, param.start);
}
else
{
ShaderGenerator temp = new ShaderGenerator();
temp.Indent();
temp.AddShaderChunk("// Generated Type: " + typeName);
BuildType(type, activeFields, temp, isDebug);
result.AppendLine(temp.GetShaderString(0, false));
}
}
}
}
private bool ProcessPredicate(Token predicate, int endLine, ref int cur, ref bool appendEndln)
{
// eval if(param)
var fieldName = predicate.GetString();
var nonwhitespace = SkipWhitespace(predicate.s, predicate.end + 1, endLine);
if (!fieldName.StartsWith("features") && activeFields.permutationCount > 0)
{
var passedPermutations = activeFields.allPermutations.instances
.Where(i => i.Contains(fieldName))
.ToList();
if (passedPermutations.Count > 0)
{
var ifdefs = KeywordUtil.GetKeywordPermutationSetConditional(
passedPermutations.Select(i => i.permutationIndex).ToList()
);
result.AppendLine(ifdefs);
// Append the rest of the line
AppendSubstring(predicate.s, nonwhitespace, true, endLine, false);
result.AppendLine("");
result.AppendLine("#endif");
return false;
}
return false;
}
else
{
// eval if(param)
if (activeFields.baseInstance.Contains(fieldName))
{
// predicate is active
// append everything before the beginning of the escape sequence
AppendSubstring(predicate.s, cur, true, predicate.start-1, false);
// continue parsing the rest of the line, starting with the first nonwhitespace character
cur = nonwhitespace;
return true;
}
else
{
// predicate is not active
if (isDebug)
{
// append everything before the beginning of the escape sequence
AppendSubstring(predicate.s, cur, true, predicate.start-1, false);
// append the rest of the line, commented out
result.Append("// ");
AppendSubstring(predicate.s, nonwhitespace, true, endLine, false);
}
else
{
// don't append anything
appendEndln = false;
}
return false;
}
}
}
private Token ParseIdentifier(string code, int start, int end)
{
if (start < end)
{
char c = code[start];
if (Char.IsLetter(c) || (c == '_'))
{
int cur = start + 1;
while (cur < end)
{
c = code[cur];
if (!(Char.IsLetterOrDigit(c) || (c == '_')))
break;
cur++;
}
return new Token(code, start, cur);
}
}
return Token.Invalid();
}
private Token ParseString(string line, int start, int end)
{
if (Expect(line, start, '"'))
{
return ParseUntil(line, start + 1, end, '"');
}
return Token.Invalid();
}
private Token ParseUntil(string line, int start, int end, char endChar)
{
int cur = start;
while (cur < end)
{
if (line[cur] == endChar)
{
return new Token(line, start, cur);
}
cur++;
}
return Token.Invalid();
}
private bool Expect(string line, int location, char expected)
{
if ((location < line.Length) && (line[location] == expected))
{
return true;
}
Error("Expected '" + expected + "'", line, location);
return false;
}
private void Error(string error, string line, int location)
{
// append the line for context
result.Append("\n");
result.Append("// ");
AppendSubstring(line, 0, true, line.Length, false);
result.Append("\n");
// append the location marker, and error description
result.Append("// ");
result.AppendSpaces(location);
result.Append("^ ");
result.Append(error);
result.Append("\n");
}
// an easier to use version of substring Append() -- explicit inclusion on each end, and checks for positive length
private void AppendSubstring(string str, int start, bool includeStart, int end, bool includeEnd)
{
if (!includeStart)
{
start++;
}
if (!includeEnd)
{
end--;
}
int count = end - start + 1;
if (count > 0)
{
result.Append(str, start, count);
}
}
}
public static void ApplyDependencies(IActiveFields activeFields, List<Dependency[]> dependsList)
{
// add active fields to queue
Queue<string> fieldsToPropagate = new Queue<string>();
foreach (var f in activeFields.fields)
{
fieldsToPropagate.Enqueue(f);
}
// foreach field in queue:
while (fieldsToPropagate.Count > 0)
{
string field = fieldsToPropagate.Dequeue();
if (activeFields.Contains(field)) // this should always be true
{
// find all dependencies of field that are not already active
foreach (Dependency[] dependArray in dependsList)
{
foreach (Dependency d in dependArray.Where(d => (d.name == field) && !activeFields.Contains(d.dependsOn)))
{
// activate them and add them to the queue
activeFields.Add(d.dependsOn);
fieldsToPropagate.Enqueue(d.dependsOn);
}
}
}
}
}
}
}