482 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using UnityEngine;
using UnityEngine.VFX;
namespace UnityEditor.VFX
{
static class VFXCodeGeneratorHelper
{
private static readonly char[] kAlpha = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
public static string GeneratePrefix(uint index)
{
if (index == 0u) return "a";
var prefix = "";
while (index != 0u)
{
prefix = kAlpha[index % kAlpha.Length] + prefix;
index /= (uint)kAlpha.Length;
}
return prefix;
}
}
class VFXShaderWriter
{
public VFXShaderWriter()
{}
public VFXShaderWriter(string initialValue)
{
builder.Append(initialValue);
}
public static string GetValueString(VFXValueType type, object value)
{
var format = "";
switch (type)
{
case VFXValueType.Boolean:
case VFXValueType.Int32:
case VFXValueType.Uint32:
case VFXValueType.Float:
format = "({0}){1}";
break;
case VFXValueType.Float2:
case VFXValueType.Float3:
case VFXValueType.Float4:
case VFXValueType.Matrix4x4:
format = "{0}{1}";
break;
default: throw new Exception("GetValueString missing type: " + type);
}
// special cases of ToString
switch (type)
{
case VFXValueType.Boolean:
value = value.ToString().ToLower();
break;
case VFXValueType.Float:
value = ((float)value).ToString("G9", CultureInfo.InvariantCulture);
break;
case VFXValueType.Float2:
value = $"({((Vector2)value).x.ToString("G9", CultureInfo.InvariantCulture)}, {((Vector2)value).y.ToString("G9", CultureInfo.InvariantCulture)})";
break;
case VFXValueType.Float3:
value = $"({((Vector3)value).x.ToString("G9", CultureInfo.InvariantCulture)}, {((Vector3)value).y.ToString("G9", CultureInfo.InvariantCulture)}, {((Vector3)value).z.ToString("G9", CultureInfo.InvariantCulture)})";
break;
case VFXValueType.Float4:
value = $"({((Vector4)value).x.ToString("G9", CultureInfo.InvariantCulture)}, {((Vector4)value).y.ToString("G9", CultureInfo.InvariantCulture)}, {((Vector4)value).z.ToString("G9", CultureInfo.InvariantCulture)}, {((Vector4)value).w.ToString("G9", CultureInfo.InvariantCulture)})";
break;
case VFXValueType.Matrix4x4:
{
var matrix = ((Matrix4x4)value).transpose;
value = "(";
for (int i = 0; i < 16; ++i)
value += string.Format(CultureInfo.InvariantCulture, i == 15 ? "{0}" : "{0},", matrix[i].ToString("G9", CultureInfo.InvariantCulture));
value += ")";
}
break;
}
return string.Format(CultureInfo.InvariantCulture, format, VFXExpression.TypeToCode(type), value);
}
public static string GetMultilineWithPrefix(string str, string linePrefix)
{
if (linePrefix.Length == 0)
return str;
if (str.Length == 0)
return linePrefix;
string[] delim = { System.Environment.NewLine, "\n" };
var lines = str.Split(delim, System.StringSplitOptions.None);
var dst = new StringBuilder(linePrefix.Length * lines.Length + str.Length);
foreach (var line in lines)
{
dst.Append(linePrefix);
dst.Append(line);
dst.Append('\n');
}
return dst.ToString(0, dst.Length - 1); // Remove the last line terminator
}
public void WriteFormat(string str, object arg0) { m_Builder.AppendFormat(str, arg0); }
public void WriteFormat(string str, object arg0, object arg1) { m_Builder.AppendFormat(str, arg0, arg1); }
public void WriteFormat(string str, object arg0, object arg1, object arg2) { m_Builder.AppendFormat(str, arg0, arg1, arg2); }
public void WriteLineFormat(string str, object arg0) { WriteFormat(str, arg0); WriteLine(); }
public void WriteLineFormat(string str, object arg0, object arg1) { WriteFormat(str, arg0, arg1); WriteLine(); }
public void WriteLineFormat(string str, object arg0, object arg1, object arg2) { WriteFormat(str, arg0, arg1, arg2); WriteLine(); }
// Generic builder method
public void Write<T>(T t)
{
m_Builder.Append(t);
}
// Optimize version to append substring and avoid useless allocation
public void Write(String s, int start, int length)
{
m_Builder.Append(s, start, length);
}
public void WriteLine<T>(T t)
{
Write(t);
WriteLine();
}
public void WriteLine()
{
m_Builder.Append('\n');
WriteIndent();
}
public void EnterScope()
{
WriteLine('{');
Indent();
}
public void ExitScope()
{
Deindent();
WriteLine('}');
}
public void ExitScopeStruct()
{
Deindent();
WriteLine("};");
}
public void ReplaceMultilineWithIndent(string tag, string src)
{
var str = m_Builder.ToString();
int startIndex = 0;
while (true)
{
int index = str.IndexOf(tag, startIndex);
if (index == -1)
break;
var lastPrefixIndex = index;
while (index > 0 && (str[index] == ' ' || str[index] == '\t'))
--index;
var prefix = str.Substring(index, lastPrefixIndex - index);
var formattedStr = GetMultilineWithPrefix(src, prefix).Substring(prefix.Length);
m_Builder.Replace(tag, formattedStr, lastPrefixIndex, tag.Length);
startIndex = index;
}
}
public void WriteMultilineWithIndent<T>(T str)
{
if (m_Indent == 0)
Write(str);
else
{
var indentStr = new StringBuilder(m_Indent * kIndentStr.Length);
for (int i = 0; i < m_Indent; ++i)
indentStr.Append(kIndentStr);
WriteMultilineWithPrefix(str, indentStr.ToString());
}
}
public void WriteMultilineWithPrefix<T>(T str, string linePrefix)
{
if (linePrefix.Length == 0)
Write(str);
else
{
var res = GetMultilineWithPrefix(str.ToString(), linePrefix);
WriteLine(res.Substring(linePrefix.Length)); // Remove first line length;
}
}
public override string ToString()
{
return m_Builder.ToString();
}
private int WritePadding(int alignment, int offset, ref int index)
{
int padding = (alignment - (offset % alignment)) % alignment;
if (padding != 0)
WriteLineFormat("uint{0} PADDING_{1};", padding == 1 ? "" : padding.ToString(), index++);
return padding;
}
public void WriteBuffer(VFXUniformMapper mapper)
{
foreach (var buffer in mapper.buffers)
{
var name = mapper.GetName(buffer);
WriteLineFormat("{0} {1};", VFXExpression.TypeToCode(buffer.valueType), name);
}
}
public void WriteTexture(VFXUniformMapper mapper)
{
foreach (var texture in mapper.textures)
{
var names = mapper.GetNames(texture);
// TODO At the moment issue all names sharing the same texture as different texture slots. This is not optimized as it required more texture binding than necessary
for (int i = 0; i < names.Count; ++i)
{
WriteLineFormat("{0} {1};", VFXExpression.TypeToCode(texture.valueType), names[i]);
if (VFXExpression.IsTexture(texture.valueType)) //Mesh doesn't require a sampler or texel helper
{
WriteLineFormat("SamplerState sampler{0};", names[i]);
WriteLineFormat("float4 {0}_TexelSize;", names[i]); // TODO This is not very good to add a uniform for each texture that is hardly ever used
}
WriteLine();
}
}
}
public void WriteEventBuffers(string baseName, int count)
{
for (int i = 0; i < count; ++i)
{
var prefix = VFXCodeGeneratorHelper.GeneratePrefix((uint)i);
WriteLineFormat("AppendStructuredBuffer<uint> {0}_{1};", baseName, prefix);
}
}
public void WriteCBuffer(VFXUniformMapper mapper, string bufferName)
{
var uniformValues = mapper.uniforms
.Where(e => !e.IsAny(VFXExpression.Flags.Constant | VFXExpression.Flags.InvalidOnCPU)) // Filter out constant expressions
.OrderByDescending(e => VFXValue.TypeToSize(e.valueType));
var uniformBlocks = new List<List<VFXExpression>>();
foreach (var value in uniformValues)
{
var block = uniformBlocks.FirstOrDefault(b => b.Sum(e => VFXValue.TypeToSize(e.valueType)) + VFXValue.TypeToSize(value.valueType) <= 4);
if (block != null)
block.Add(value);
else
uniformBlocks.Add(new List<VFXExpression>() { value });
}
if (uniformBlocks.Count > 0)
{
WriteLineFormat("CBUFFER_START({0})", bufferName);
Indent();
int paddingIndex = 0;
foreach (var block in uniformBlocks)
{
int currentSize = 0;
foreach (var value in block)
{
string type = VFXExpression.TypeToUniformCode(value.valueType);
string name = mapper.GetName(value);
if (name.StartsWith("unity_")) //Reserved unity variable name (could be filled manually see : VFXCameraUpdate)
continue;
currentSize += VFXExpression.TypeToSize(value.valueType);
WriteLineFormat("{0} {1};", type, name);
}
WritePadding(4, currentSize, ref paddingIndex);
}
Deindent();
WriteLine("CBUFFER_END");
}
}
public void WriteAttributeStruct(IEnumerable<VFXAttribute> attributes, string name)
{
WriteLineFormat("struct {0}", name);
WriteLine("{");
Indent();
foreach (var attribute in attributes)
WriteLineFormat("{0} {1};", VFXExpression.TypeToCode(attribute.type), attribute.name);
Deindent();
WriteLine("};");
}
private string AggregateParameters(List<string> parameters)
{
return parameters.Count == 0 ? "" : parameters.Aggregate((a, b) => a + ", " + b);
}
private static string GetFunctionParameterType(VFXValueType type)
{
switch (type)
{
case VFXValueType.Texture2D: return "VFXSampler2D";
case VFXValueType.Texture2DArray: return "VFXSampler2DArray";
case VFXValueType.Texture3D: return "VFXSampler3D";
case VFXValueType.TextureCube: return "VFXSamplerCube";
case VFXValueType.TextureCubeArray: return "VFXSamplerCubeArray";
default:
return VFXExpression.TypeToCode(type);
}
}
private static string GetFunctionParameterName(VFXExpression expression, Dictionary<VFXExpression, string> names)
{
var expressionName = names[expression];
switch (expression.valueType)
{
case VFXValueType.Texture2D:
case VFXValueType.Texture2DArray:
case VFXValueType.Texture3D:
case VFXValueType.TextureCube:
case VFXValueType.TextureCubeArray: return string.Format("GetVFXSampler({0}, {1})", expressionName, ("sampler" + expressionName));
default:
return expressionName;
}
}
private static string GetInputModifier(VFXAttributeMode mode)
{
if ((mode & VFXAttributeMode.Write) != 0)
return "inout ";
return string.Empty;
}
public struct FunctionParameter
{
public string name;
public VFXExpression expression;
public VFXAttributeMode mode;
}
public void WriteBlockFunction(VFXExpressionMapper mapper, string functionName, string source, IEnumerable<FunctionParameter> parameters, string commentMethod)
{
var parametersCode = new List<string>();
foreach (var parameter in parameters)
{
var inputModifier = GetInputModifier(parameter.mode);
var parameterType = GetFunctionParameterType(parameter.expression.valueType);
parametersCode.Add(string.Format("{0}{1} {2}", inputModifier, parameterType, parameter.name));
}
WriteFormat("void {0}({1})", functionName, AggregateParameters(parametersCode));
if (!string.IsNullOrEmpty(commentMethod))
{
WriteFormat(" /*{0}*/", commentMethod);
}
WriteLine();
EnterScope();
if (source != null)
WriteMultilineWithIndent(source);
ExitScope();
}
public void WriteCallFunction(string functionName, IEnumerable<FunctionParameter> parameters, VFXExpressionMapper mapper, Dictionary<VFXExpression, string> variableNames)
{
var parametersCode = new List<string>();
foreach (var parameter in parameters)
{
var inputModifier = GetInputModifier(parameter.mode);
parametersCode.Add(string.Format("{1}{0}", GetFunctionParameterName(parameter.expression, variableNames), string.IsNullOrEmpty(inputModifier) ? string.Empty : string.Format(" /*{0}*/", inputModifier)));
}
WriteLineFormat("{0}({1});", functionName, AggregateParameters(parametersCode));
}
public void WriteAssignement(VFXValueType type, string variableName, string value)
{
var format = value == "0" ? "{1} = ({0}){2};" : "{1} = {2};";
WriteFormat(format, VFXExpression.TypeToCode(type), variableName, value);
}
public void WriteVariable(VFXValueType type, string variableName, string value)
{
if (!VFXExpression.IsTypeValidOnGPU(type))
throw new ArgumentException(string.Format("Invalid GPU Type: {0}", type));
WriteFormat("{0} ", VFXExpression.TypeToCode(type));
WriteAssignement(type, variableName, value);
}
public void WriteDeclaration(VFXValueType type, string variableName)
{
if (!VFXExpression.IsTypeValidOnGPU(type))
throw new ArgumentException(string.Format("Invalid GPU Type: {0}", type));
WriteFormat("{0} {1};\n", VFXExpression.TypeToCode(type), variableName);
}
public void WriteDeclaration(VFXValueType type, string variableName, string semantic)
{
if (!VFXExpression.IsTypeValidOnGPU(type))
throw new ArgumentException(string.Format("Invalid GPU Type: {0}", type));
WriteFormat("VFX_OPTIONAL_INTERPOLATION {0} {1} : {2};\n", VFXExpression.TypeToCode(type), variableName, semantic);
}
public void WriteVariable(VFXExpression exp, Dictionary<VFXExpression, string> variableNames)
{
if (!variableNames.ContainsKey(exp))
{
string entry;
if (exp.Is(VFXExpression.Flags.Constant))
entry = exp.GetCodeString(null); // Patch constant directly
else
{
foreach (var parent in exp.parents)
WriteVariable(parent, variableNames);
// Generate a new variable name
entry = "tmp_" + VFXCodeGeneratorHelper.GeneratePrefix((uint)variableNames.Count());
string value = exp.GetCodeString(exp.parents.Select(p => variableNames[p]).ToArray());
WriteVariable(exp.valueType, entry, value);
WriteLine();
}
variableNames[exp] = entry;
}
}
public StringBuilder builder { get { return m_Builder; } }
// Private stuff
private void Indent()
{
++m_Indent;
Write(kIndentStr);
}
private void Deindent()
{
if (m_Indent == 0)
throw new InvalidOperationException("Cannot de-indent as current indentation is 0");
--m_Indent;
m_Builder.Remove(m_Builder.Length - kIndentStr.Length, kIndentStr.Length); // remove last indent
}
private void WriteIndent()
{
for (int i = 0; i < m_Indent; ++i)
m_Builder.Append(kIndentStr);
}
private StringBuilder m_Builder = new StringBuilder();
private int m_Indent = 0;
private const string kIndentStr = " ";
}
}