forked from BilalY/Rasagar
1094 lines
50 KiB
C#
1094 lines
50 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
using System.Text.RegularExpressions;
|
|
using System.Globalization;
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.VFX;
|
|
using UnityEngine.Profiling;
|
|
using UnityEngine.Rendering;
|
|
|
|
using UnityEditor.ShaderGraph;
|
|
using UnityEditor.Graphing.Util;
|
|
using UnityEditor.ShaderGraph.Serialization;
|
|
|
|
namespace UnityEditor.VFX
|
|
{
|
|
static class VFXCodeGenerator
|
|
{
|
|
public const uint nbThreadsPerGroup = 64u;
|
|
|
|
private static string GetIndent(string src, int index)
|
|
{
|
|
var indent = "";
|
|
index--;
|
|
while (index > 0 && (src[index] == ' ' || src[index] == '\t'))
|
|
{
|
|
indent = src[index] + indent;
|
|
index--;
|
|
}
|
|
return indent;
|
|
}
|
|
|
|
//This function insure to keep padding while replacing a specific string
|
|
public static void ReplaceMultiline(StringBuilder target, string targetQuery, StringBuilder value)
|
|
{
|
|
Profiler.BeginSample("ReplaceMultiline");
|
|
|
|
string[] delim = { System.Environment.NewLine, "\n" };
|
|
var valueLines = value.ToString().Split(delim, System.StringSplitOptions.None);
|
|
// For some reasons, just calling Replace(...) without any index data is orders of magnitude
|
|
// slower than searching a copy of the string to get the index first. So both codepaths do
|
|
// exactly that.
|
|
if (valueLines.Length <= 1)
|
|
{
|
|
var replacement = value.ToString();
|
|
int startIndex = 0;
|
|
while (true)
|
|
{
|
|
var targetCopy = target.ToString();
|
|
var index = targetCopy.IndexOf(targetQuery, startIndex, StringComparison.Ordinal);
|
|
if (index == -1)
|
|
break;
|
|
target.Replace(targetQuery, replacement, index, targetQuery.Length);
|
|
startIndex = index;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int startIndex = 0;
|
|
while (true)
|
|
{
|
|
var targetCopy = target.ToString();
|
|
var index = targetCopy.IndexOf(targetQuery, startIndex, StringComparison.Ordinal);
|
|
if (index == -1)
|
|
break;
|
|
var indent = GetIndent(targetCopy, index);
|
|
var currentValue = new StringBuilder();
|
|
foreach (var line in valueLines)
|
|
{
|
|
currentValue.Append(indent + line + '\n');
|
|
}
|
|
var currentValueString = currentValue.ToString();
|
|
var toReplace = indent + targetQuery;
|
|
index -= indent.Length;
|
|
target.Replace(toReplace, currentValueString, index, toReplace.Length);
|
|
startIndex = index;
|
|
}
|
|
}
|
|
|
|
Profiler.EndSample();
|
|
}
|
|
|
|
internal static VFXShaderWriter GenerateLoadAttribute(string matching, VFXContext context, VFXTaskCompiledData taskData)
|
|
{
|
|
var r = new VFXShaderWriter();
|
|
|
|
var regex = new Regex(matching);
|
|
var attributesFromContext = context.GetData().GetAttributes().Where(o => regex.IsMatch(o.attrib.name)).ToArray();
|
|
var attributesSource = attributesFromContext.Where(a => context.GetData().IsSourceAttributeUsed(a.attrib, context)).ToArray();
|
|
var attributesCurrent = attributesFromContext.Where(a => context.GetData().IsCurrentAttributeUsed(a.attrib, context) || (context.contextType == VFXContextType.Init && context.GetData().IsAttributeStored(a.attrib))).ToArray();
|
|
|
|
//< Current Attribute
|
|
foreach (var attribute in attributesCurrent.Select(o => o.attrib))
|
|
{
|
|
var name = attribute.GetNameInCode(VFXAttributeLocation.Current);
|
|
if (attribute.name != VFXAttribute.EventCount.name)
|
|
{
|
|
if (context.contextType != VFXContextType.Init && context.GetData().IsAttributeStored(attribute))
|
|
{
|
|
r.WriteAssignement(attribute.type, name, context.GetData().GetLoadAttributeCode(attribute, VFXAttributeLocation.Current));
|
|
}
|
|
else
|
|
{
|
|
r.WriteAssignement(attribute.type, name, attribute.value.GetCodeString(null));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
r.WriteAssignement(attribute.type, name, attribute.value.GetCodeString(null));
|
|
for (uint i = 0; i < taskData.linkedEventOut.Length; ++i)
|
|
{
|
|
r.WriteLine();
|
|
var linkedEventOut = taskData.linkedEventOut[i];
|
|
var capacity = (uint)linkedEventOut.data.GetSettingValue("capacity");
|
|
r.WriteFormat("uint {0}_{1} = 0u; uint {0}_{1}_Capacity = {2};", VFXAttribute.EventCount.name, VFXCodeGeneratorHelper.GeneratePrefix(i), capacity);
|
|
}
|
|
}
|
|
r.WriteLine();
|
|
}
|
|
|
|
//< Source Attribute (default temporary behavior, source is always the initial current value except for init context)
|
|
foreach (var attribute in attributesSource.Select(o => o.attrib))
|
|
{
|
|
var name = attribute.GetNameInCode(VFXAttributeLocation.Source);
|
|
if (context.contextType == VFXContextType.Init)
|
|
{
|
|
r.WriteAssignement(attribute.type, name, context.GetData().GetLoadAttributeCode(attribute, VFXAttributeLocation.Source));
|
|
}
|
|
else
|
|
{
|
|
if (attributesCurrent.Any(o => o.attrib.name == attribute.name))
|
|
{
|
|
var reference = new VFXAttributeExpression(new VFXAttribute(attribute.name, attribute.value, string.Empty), VFXAttributeLocation.Current);
|
|
r.WriteAssignement(reference.valueType, name, reference.GetCodeString(null));
|
|
}
|
|
else
|
|
{
|
|
r.WriteAssignement(attribute.type, name, attribute.value.GetCodeString(null));
|
|
}
|
|
}
|
|
r.WriteLine();
|
|
}
|
|
return r;
|
|
}
|
|
|
|
private const string eventListOutName = "eventListOut";
|
|
|
|
static private VFXShaderWriter GenerateStoreAttribute(string matching, VFXContext context, uint linkedOutCount)
|
|
{
|
|
var r = new VFXShaderWriter();
|
|
var regex = new Regex(matching);
|
|
|
|
var attributesFromContext = context.GetData().GetAttributes().Where(o => regex.IsMatch(o.attrib.name) &&
|
|
context.GetData().IsAttributeStored(o.attrib) &&
|
|
(context.contextType == VFXContextType.Init || context.GetData().IsCurrentAttributeWritten(o.attrib, context))).ToArray();
|
|
|
|
foreach (var attribute in attributesFromContext.Select(o => o.attrib))
|
|
{
|
|
r.Write(context.GetData().GetStoreAttributeCode(attribute, new VFXAttributeExpression(attribute).GetCodeString(null)));
|
|
r.WriteLine(';');
|
|
}
|
|
|
|
if (regex.IsMatch(VFXAttribute.EventCount.name))
|
|
{
|
|
for (uint i = 0; i < linkedOutCount; ++i)
|
|
{
|
|
var prefix = VFXCodeGeneratorHelper.GeneratePrefix(i);
|
|
r.WriteLineFormat(@"
|
|
for (uint i_{0} = 0; i_{0} < min({1}_{0}, {1}_{0}_Capacity); ++i_{0})
|
|
AppendEventBuffer({2}_{0}, index, {1}_{0}_Capacity, instanceIndex);
|
|
AppendEventTotalCount({2}_{0}, min({1}_{0}, {1}_{0}_Capacity), instanceIndex);
|
|
",
|
|
prefix,
|
|
VFXAttribute.EventCount.name,
|
|
eventListOutName);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
static internal VFXShaderWriter GenerateSetInstancingIndices(VFXContext context)
|
|
{
|
|
var r = new VFXShaderWriter();
|
|
|
|
// Hardcoded, duplicated from VFXParticleCommon.template
|
|
r.WriteLine("uint instanceIndex, instanceActiveIndex, instanceCurrentIndex;");
|
|
r.WriteLine("index = VFXInitInstancing(index, instanceIndex, instanceActiveIndex, instanceCurrentIndex);");
|
|
|
|
return r;
|
|
}
|
|
|
|
static internal VFXShaderWriter GenerateLoadParameter(string matching, VFXNamedExpression[] namedExpressions, Dictionary<VFXExpression, string> expressionToName)
|
|
{
|
|
var r = new VFXShaderWriter();
|
|
var regex = new Regex(matching);
|
|
|
|
var filteredNamedExpressions = namedExpressions.Where(o => regex.IsMatch(o.name) &&
|
|
!(expressionToName.ContainsKey(o.exp) && expressionToName[o.exp] == o.name)); // if parameter already in the global scope, there's nothing to do
|
|
|
|
bool needScope = false;
|
|
foreach (var namedExpression in filteredNamedExpressions)
|
|
{
|
|
r.WriteVariable(namedExpression.exp.valueType, namedExpression.name, "0");
|
|
r.WriteLine();
|
|
needScope = true;
|
|
}
|
|
|
|
if (needScope)
|
|
{
|
|
var expressionToNameLocal = new Dictionary<VFXExpression, string>(expressionToName);
|
|
r.EnterScope();
|
|
foreach (var namedExpression in filteredNamedExpressions)
|
|
{
|
|
if (!expressionToNameLocal.ContainsKey(namedExpression.exp))
|
|
{
|
|
r.WriteVariable(namedExpression.exp, expressionToNameLocal);
|
|
r.WriteLine();
|
|
}
|
|
r.WriteAssignement(namedExpression.exp.valueType, namedExpression.name, expressionToNameLocal[namedExpression.exp]);
|
|
r.WriteLine();
|
|
}
|
|
r.ExitScope();
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
public static StringBuilder Build(
|
|
VFXContext context,
|
|
VFXTask task,
|
|
VFXCompilationMode compilationMode,
|
|
VFXTaskCompiledData taskData,
|
|
HashSet<string> dependencies,
|
|
bool forceShadeDebugSymbols,
|
|
out List<string> errors)
|
|
{
|
|
string templatePath = null;
|
|
if (!string.IsNullOrEmpty(task.templatePath))
|
|
{
|
|
templatePath = $"{task.templatePath}.template";
|
|
dependencies.Add(AssetDatabase.AssetPathToGUID(templatePath));
|
|
}
|
|
|
|
return Build(context, task, templatePath, compilationMode, taskData, dependencies, forceShadeDebugSymbols, out errors);
|
|
}
|
|
|
|
private static void GetFunctionName(VFXBlock block, out string functionName, out string comment)
|
|
{
|
|
var settings = block.GetSettings(true).ToArray();
|
|
if (settings.Length > 0)
|
|
{
|
|
comment = "";
|
|
int hash = 0;
|
|
foreach (var setting in settings.Where(x => x.valid))
|
|
{
|
|
var value = setting.value;
|
|
hash = (hash * 397) ^ (value?.GetHashCode() ?? 1);
|
|
if (setting.visibility.HasFlag(VFXSettingAttribute.VisibleFlags.InGeneratedCodeComments))
|
|
{
|
|
comment += setting + " ";
|
|
}
|
|
}
|
|
functionName = $"{block.GetType().Name}_{hash:X}";
|
|
}
|
|
else
|
|
{
|
|
comment = null;
|
|
functionName = block.GetType().Name;
|
|
}
|
|
}
|
|
|
|
static private string FormatPath(string path)
|
|
{
|
|
return Path.GetFullPath(path)
|
|
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
|
|
#if !UNITY_EDITOR_LINUX
|
|
.ToLowerInvariant()
|
|
#endif
|
|
;
|
|
}
|
|
|
|
static IEnumerable<Match> GetUniqueMatches(string regexStr, string src)
|
|
{
|
|
var regex = new Regex(regexStr);
|
|
var matches = regex.Matches(src);
|
|
return matches.Cast<Match>().GroupBy(m => m.Groups[0].Value).Select(g => g.First());
|
|
}
|
|
|
|
static private StringBuilder GetFlattenedTemplateContent(string path, List<string> includes, IEnumerable<string> defines, HashSet<string> dependencies)
|
|
{
|
|
var formattedPath = FormatPath(path);
|
|
|
|
if (includes.Contains(formattedPath))
|
|
{
|
|
var includeHierarchy = new StringBuilder(string.Format("Cyclic VFXInclude dependency detected: {0}\n", formattedPath));
|
|
foreach (var str in Enumerable.Reverse<string>(includes))
|
|
includeHierarchy.Append(str + '\n');
|
|
throw new InvalidOperationException(includeHierarchy.ToString());
|
|
}
|
|
|
|
includes.Add(formattedPath);
|
|
var templateContent = new StringBuilder(System.IO.File.ReadAllText(formattedPath));
|
|
|
|
foreach (var match in GetUniqueMatches("\\${VFXInclude(RP|)\\(\\\"(.*?)\\\"\\)(,.*)?}", templateContent.ToString()))
|
|
{
|
|
var groups = match.Groups;
|
|
var renderPipelineInclude = groups[1].Value == "RP";
|
|
var includePath = groups[2].Value;
|
|
|
|
|
|
if (groups.Count > 3 && !String.IsNullOrEmpty(groups[2].Value))
|
|
{
|
|
var allDefines = groups[3].Value.Split(new char[] { ',', ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
|
var neededDefines = allDefines.Where(d => d[0] != '!');
|
|
var forbiddenDefines = allDefines.Except(neededDefines).Select(d => d.Substring(1));
|
|
if (!neededDefines.All(d => defines.Contains(d)) || forbiddenDefines.Any(d => defines.Contains(d)))
|
|
{
|
|
ReplaceMultiline(templateContent, groups[0].Value, new StringBuilder());
|
|
continue;
|
|
}
|
|
}
|
|
|
|
string absolutePath;
|
|
if (renderPipelineInclude)
|
|
absolutePath = VFXLibrary.currentSRPBinder.templatePath + "/" + includePath;
|
|
else
|
|
absolutePath = VisualEffectGraphPackageInfo.assetPackagePath + "/" + includePath;
|
|
dependencies.Add(AssetDatabase.AssetPathToGUID(absolutePath));
|
|
|
|
var includeBuilder = GetFlattenedTemplateContent(absolutePath, includes, defines, dependencies);
|
|
ReplaceMultiline(templateContent, groups[0].Value, includeBuilder);
|
|
}
|
|
|
|
includes.Remove(formattedPath);
|
|
return templateContent;
|
|
}
|
|
|
|
static private void SubstituteMacros(StringBuilder builder)
|
|
{
|
|
var definesToCode = new Dictionary<string, string>();
|
|
var source = builder.ToString();
|
|
Regex beginRegex = new Regex("\\${VFXBegin:(.*)}");
|
|
|
|
int currentPos = -1;
|
|
int builderOffset = 0;
|
|
while ((currentPos = source.IndexOf("${", StringComparison.Ordinal)) != -1)
|
|
{
|
|
int endPos = source.IndexOf('}', currentPos);
|
|
if (endPos == -1)
|
|
throw new FormatException("Ill-formed VFX tag (Missing closing brace");
|
|
|
|
var tag = source.Substring(currentPos, endPos - currentPos + 1);
|
|
// Replace any tag found
|
|
string macro;
|
|
if (definesToCode.TryGetValue(tag, out macro))
|
|
{
|
|
builder.Remove(currentPos + builderOffset, tag.Length);
|
|
var indentedMacro = macro.Replace("\n", "\n" + GetIndent(source, currentPos));
|
|
builder.Insert(currentPos + builderOffset, indentedMacro);
|
|
}
|
|
else
|
|
{
|
|
const string endStr = "${VFXEnd}";
|
|
var match = beginRegex.Match(source, currentPos, tag.Length);
|
|
if (match.Success)
|
|
{
|
|
var macroStartPos = match.Index + match.Length;
|
|
var macroEndCodePos = source.IndexOf(endStr, macroStartPos);
|
|
if (macroEndCodePos == -1)
|
|
throw new FormatException("${VFXBegin} found without ${VFXEnd}");
|
|
|
|
var defineStr = "${" + match.Groups[1].Value + "}";
|
|
definesToCode[defineStr] = source.Substring(macroStartPos, macroEndCodePos - macroStartPos);
|
|
|
|
// Remove the define in builder
|
|
builder.Remove(match.Index + builderOffset, macroEndCodePos - match.Index + endStr.Length);
|
|
}
|
|
else if (tag == endStr)
|
|
throw new FormatException("${VFXEnd} found without ${VFXBegin}");
|
|
else // Remove undefined tag
|
|
builder.Remove(currentPos + builderOffset, tag.Length);
|
|
}
|
|
|
|
builderOffset += currentPos;
|
|
source = builder.ToString(builderOffset, builder.Length - builderOffset);
|
|
}
|
|
}
|
|
|
|
internal static Dictionary<VFXExpression, string> BuildExpressionToName(VFXContext context, VFXTaskCompiledData taskData)
|
|
{
|
|
var expressionToName = new Dictionary<VFXExpression, string>(taskData.uniformMapper.expressionToCode);
|
|
foreach (var attribute in context.GetData().GetAttributes())
|
|
{
|
|
var expression = new VFXAttributeExpression(attribute.attrib);
|
|
expressionToName.Add(expression, expression.GetCodeString(null));
|
|
}
|
|
return expressionToName;
|
|
}
|
|
|
|
internal static void BuildContextBlocks(VFXContext context, VFXTaskCompiledData taskData, Dictionary<VFXExpression, string> expressionToName,
|
|
out VFXShaderWriter blockFunction,
|
|
out VFXShaderWriter blockCallFunction,
|
|
out VFXShaderWriter blockIncludes,
|
|
out VFXShaderWriter blockDefines)
|
|
{
|
|
//< Block processor
|
|
blockFunction = new VFXShaderWriter();
|
|
blockCallFunction = new VFXShaderWriter();
|
|
blockIncludes = new VFXShaderWriter();
|
|
blockDefines = new VFXShaderWriter();
|
|
|
|
var blockDeclared = new HashSet<string>();
|
|
var includesProcessed = new HashSet<string>();
|
|
var defineProcessed = new HashSet<string>();
|
|
|
|
int cpt = 0;
|
|
foreach (var current in context.activeFlattenedChildrenWithImplicit)
|
|
{
|
|
// Custom HLSL Blocks
|
|
if (current is IHLSLCodeHolder hlslCodeHolder)
|
|
{
|
|
blockFunction.Write(hlslCodeHolder.customCode);
|
|
foreach (var includePath in hlslCodeHolder.includes)
|
|
{
|
|
if (includesProcessed.Add(includePath))
|
|
{
|
|
blockIncludes.WriteLine($"#include \"{includePath}\"");
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var define in current.defines)
|
|
{
|
|
if (defineProcessed.Add(define))
|
|
{
|
|
blockDefines.WriteLineFormat("#define {0}{1}", define, define.Contains(' ') ? "" : " 1");
|
|
}
|
|
}
|
|
BuildBlock(taskData, blockFunction, blockCallFunction, blockDeclared, expressionToName, current, ref cpt);
|
|
}
|
|
|
|
// Custom HLSL Operators
|
|
var customCodeProcessed = new HashSet<string>();
|
|
foreach (var hlslCodeHolder in taskData.hlslCodeHolders)
|
|
{
|
|
var customCode = hlslCodeHolder.customCode;
|
|
if (customCodeProcessed.Add(customCode))
|
|
{
|
|
blockFunction.Write(customCode);
|
|
}
|
|
|
|
foreach (var includePath in hlslCodeHolder.includes)
|
|
{
|
|
if (includesProcessed.Add(includePath))
|
|
{
|
|
blockIncludes.WriteLine($"#include \"{includePath}\"");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void BuildParameterBuffer(VFXTaskCompiledData taskData, IEnumerable<string> filteredOutTextures, out string parameterBufferContent, out bool needsGraphValueStruct) //TODO: pass all in one? Do we need some info out of that method?
|
|
{
|
|
var parameterBuffer = new VFXShaderWriter();
|
|
needsGraphValueStruct = parameterBuffer.WriteGraphValuesStruct(taskData.uniformMapper);
|
|
parameterBuffer.WriteLine();
|
|
parameterBuffer.WriteBufferTypeDeclaration(taskData.bufferUsage.Values);
|
|
parameterBuffer.WriteLine();
|
|
parameterBuffer.WriteBuffer(taskData.uniformMapper, taskData.bufferUsage);
|
|
parameterBuffer.WriteLine();
|
|
parameterBuffer.WriteTexture(taskData.uniformMapper, filteredOutTextures);
|
|
parameterBufferContent = parameterBuffer.ToString();
|
|
}
|
|
|
|
internal static void BuildVertexProperties(VFXTaskCompiledData taskData, out string vertexProperties)
|
|
{
|
|
if (taskData.SGInputs != null)
|
|
{
|
|
var vertexPropertiesWriter = new VFXShaderWriter();
|
|
var expressionToNameLocal = new Dictionary<VFXExpression, string>(taskData.uniformMapper.expressionToCode);
|
|
|
|
// Expression tree
|
|
foreach (var input in taskData.SGInputs.vertInputs)
|
|
vertexPropertiesWriter.WriteVariable(input.Value, expressionToNameLocal);
|
|
|
|
vertexPropertiesWriter.WriteLine();
|
|
|
|
// Assignment
|
|
foreach (var input in taskData.SGInputs.vertInputs)
|
|
{
|
|
var (name, exp) = (input.Key, input.Value);
|
|
vertexPropertiesWriter.WriteAssignement(exp.valueType, $"properties.{name}", expressionToNameLocal[exp]);
|
|
vertexPropertiesWriter.WriteLine();
|
|
}
|
|
|
|
vertexProperties = vertexPropertiesWriter.ToString();
|
|
}
|
|
else
|
|
{
|
|
vertexProperties = string.Empty;
|
|
}
|
|
}
|
|
|
|
|
|
internal static void BuildFragInputsGenerationRayTracing(VFXTaskCompiledData taskData, bool useFragInputs, out string buildFragInputsGeneration)
|
|
{
|
|
// Frag Inputs for Ray Tracing - Skips the interpolant struct generation and assigns the Frag Inputs directly.
|
|
|
|
if (taskData.SGInputs != null)
|
|
{
|
|
var fragInputsGeneration = new VFXShaderWriter();
|
|
string surfaceSetter = useFragInputs ? "output.vfx" : "output";
|
|
|
|
var expressionToNameLocal = new Dictionary<VFXExpression, string>(taskData.uniformMapper.expressionToCode);
|
|
|
|
// Expression tree
|
|
foreach (var interp in taskData.SGInputs.interpolators)
|
|
fragInputsGeneration.WriteVariable(interp.Key, expressionToNameLocal);
|
|
fragInputsGeneration.WriteLine();
|
|
|
|
|
|
foreach (var input in taskData.SGInputs.fragInputs)
|
|
{
|
|
var (name, exp) = (input.Key, input.Value);
|
|
string inputExpStr;
|
|
|
|
if (exp.Is(VFXExpression.Flags.Constant))
|
|
inputExpStr = exp.GetCodeString(null); // From constant
|
|
else if (taskData.SGInputs.IsInterpolant(exp))
|
|
{
|
|
inputExpStr = expressionToNameLocal[exp]; // From interpolator
|
|
}
|
|
else
|
|
inputExpStr = $"graphValues.{taskData.uniformMapper.GetName(exp)}"; // From uniform
|
|
|
|
fragInputsGeneration.WriteAssignement(exp.valueType, $"{surfaceSetter}.{name}", inputExpStr);
|
|
fragInputsGeneration.WriteLine();
|
|
}
|
|
|
|
buildFragInputsGeneration = fragInputsGeneration.ToString();
|
|
}
|
|
else
|
|
{
|
|
buildFragInputsGeneration = string.Empty;
|
|
}
|
|
}
|
|
internal static void BuildInterpolatorBlocks(VFXTaskCompiledData taskData,
|
|
out string interpolatorsGeneration)
|
|
{
|
|
if (taskData.SGInputs != null)
|
|
{
|
|
var interpolantsGenerationWriter = new VFXShaderWriter();
|
|
var expressionToNameLocal = new Dictionary<VFXExpression, string>(taskData.uniformMapper.expressionToCode);
|
|
|
|
// Expression tree
|
|
foreach (var interp in taskData.SGInputs.interpolators)
|
|
interpolantsGenerationWriter.WriteVariable(interp.Key, expressionToNameLocal);
|
|
|
|
interpolantsGenerationWriter.WriteLine();
|
|
|
|
// Assignment
|
|
foreach (var interp in taskData.SGInputs.interpolators)
|
|
{
|
|
var (exp, name) = (interp.Key, interp.Value);
|
|
interpolantsGenerationWriter.WriteAssignement(exp.valueType, $"output.{name}", expressionToNameLocal[exp]);
|
|
interpolantsGenerationWriter.WriteLine();
|
|
}
|
|
|
|
interpolatorsGeneration = interpolantsGenerationWriter.ToString();
|
|
}
|
|
else
|
|
{
|
|
interpolatorsGeneration = string.Empty;
|
|
}
|
|
}
|
|
|
|
internal static void BuildFragInputsGeneration(VFXTaskCompiledData taskData, bool useFragInputs, out string buildFragInputsGeneration)
|
|
{
|
|
if (taskData.SGInputs != null)
|
|
{
|
|
var fragInputsGeneration = new VFXShaderWriter();
|
|
string surfaceSetter = useFragInputs ? "output.vfx" : "output";
|
|
|
|
foreach (var input in taskData.SGInputs.fragInputs)
|
|
{
|
|
var (name, exp) = (input.Key, input.Value);
|
|
string inputExpStr;
|
|
|
|
if (exp.Is(VFXExpression.Flags.Constant))
|
|
inputExpStr = exp.GetCodeString(null); // From constant
|
|
else if (taskData.SGInputs.IsInterpolant(exp))
|
|
inputExpStr = $"input.{taskData.SGInputs.GetInterpolantName(exp)}"; // From interpolator
|
|
else
|
|
inputExpStr = $"graphValues.{taskData.uniformMapper.GetName(exp)}"; // From uniform
|
|
|
|
fragInputsGeneration.WriteAssignement(exp.valueType, $"{surfaceSetter}.{name}", inputExpStr);
|
|
fragInputsGeneration.WriteLine();
|
|
}
|
|
|
|
buildFragInputsGeneration = fragInputsGeneration.ToString();
|
|
}
|
|
else
|
|
{
|
|
buildFragInputsGeneration = string.Empty;
|
|
}
|
|
}
|
|
|
|
internal static void BuildPixelPropertiesAssign(VFXTaskCompiledData taskData, bool useFragInputs, out string buildFragInputsGeneration)
|
|
{
|
|
if (taskData.SGInputs != null)
|
|
{
|
|
var fragInputsGeneration = new VFXShaderWriter();
|
|
var surfaceGetter = useFragInputs ? "fragInputs.vfx" : "fragInputs";
|
|
|
|
foreach (var input in taskData.SGInputs.fragInputs)
|
|
{
|
|
var (name, exp) = (input.Key, input.Value);
|
|
fragInputsGeneration.WriteAssignement(exp.valueType, $"properties.{name}", $"{surfaceGetter}.{name}");
|
|
fragInputsGeneration.WriteLine();
|
|
}
|
|
|
|
buildFragInputsGeneration = fragInputsGeneration.ToString();
|
|
}
|
|
else
|
|
{
|
|
buildFragInputsGeneration = string.Empty;
|
|
}
|
|
}
|
|
|
|
internal static void BuildFillGraphValues(VFXTaskCompiledData taskData, VFXDataParticle.GraphValuesLayout graphValuesLayout,
|
|
VFXUniformMapper systemUniformMapper,
|
|
out string fillGraphValues)
|
|
{
|
|
var fillGraphValuesShaderWriter = new VFXShaderWriter();
|
|
fillGraphValuesShaderWriter.GenerateFillGraphValuesStruct(taskData.uniformMapper, graphValuesLayout);
|
|
fillGraphValues = fillGraphValuesShaderWriter.ToString();
|
|
}
|
|
|
|
internal static void BuildLoadContextData(VFXDataParticle.GraphValuesLayout graphValuesLayout, out string loadContextData)
|
|
{
|
|
var loadContextDataShaderWriter = new VFXShaderWriter();
|
|
loadContextDataShaderWriter.GenerateLoadContextData(graphValuesLayout);
|
|
loadContextData = loadContextDataShaderWriter.ToString();
|
|
}
|
|
|
|
static private StringBuilder Build(
|
|
VFXContext context,
|
|
VFXTask task,
|
|
string templatePath,
|
|
VFXCompilationMode compilationMode,
|
|
VFXTaskCompiledData taskData,
|
|
HashSet<string> dependencies,
|
|
bool enableShaderDebugSymbols,
|
|
out List<string> errors)
|
|
{
|
|
errors = null;
|
|
if (!context.SetupCompilation())
|
|
return null;
|
|
|
|
var contextData = context.GetData();
|
|
|
|
// Readable identifier for the profile marker
|
|
string shaderIdStr = string.Empty;
|
|
if (!string.IsNullOrEmpty(contextData.title))
|
|
shaderIdStr += contextData.title;
|
|
if (!string.IsNullOrEmpty(context.name))
|
|
shaderIdStr += "/" + context.name;
|
|
if (!string.IsNullOrEmpty(context.label))
|
|
shaderIdStr += "/" + context.label;
|
|
if (!string.IsNullOrEmpty(task.name))
|
|
shaderIdStr += "/" + task.name;
|
|
shaderIdStr = shaderIdStr.Replace("\n", " ");
|
|
|
|
Profiler.BeginSample($"GenerateShader ({shaderIdStr})");
|
|
|
|
if (context is IVFXShaderGraphOutput shaderGraphOutput)
|
|
{
|
|
var shaderGraph = shaderGraphOutput.GetShaderGraph();
|
|
if (shaderGraph != null && shaderGraph.generatesWithShaderGraph)
|
|
{
|
|
var result = TryBuildFromShaderGraph(context, taskData, out errors);
|
|
context.EndCompilation();
|
|
Profiler.EndSample();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
var allAdditionalDefines = context.additionalDefines.Concat(task.additionalDefines ?? Enumerable.Empty<string>());
|
|
var stringBuilder = GetFlattenedTemplateContent(templatePath, new List<string>(), allAdditionalDefines, dependencies);
|
|
|
|
var allCurrentAttributes = contextData.GetAttributes().Where(a =>
|
|
(contextData.IsCurrentAttributeUsed(a.attrib, context)) ||
|
|
(context.contextType == VFXContextType.Init && contextData.IsAttributeStored(a.attrib))); // In init, needs to declare all stored attributes for intialization
|
|
|
|
var allSourceAttributes = contextData.GetAttributes().Where(a => (contextData.IsSourceAttributeUsed(a.attrib, context)));
|
|
|
|
var globalDeclaration = new VFXShaderWriter();
|
|
globalDeclaration.WriteBufferTypeDeclaration(taskData.bufferUsage.Values);
|
|
globalDeclaration.WriteLine();
|
|
var particleData = (contextData as VFXDataParticle);
|
|
var systemUniformMapper = particleData.systemUniformMapper;
|
|
taskData.uniformMapper.OverrideUniformsNamesWithOther(systemUniformMapper);
|
|
var needsGraphValueStruct = globalDeclaration.WriteGraphValuesStruct(taskData.uniformMapper);
|
|
globalDeclaration.WriteLine();
|
|
|
|
globalDeclaration.WriteBuffer(taskData.uniformMapper, taskData.bufferUsage);
|
|
globalDeclaration.WriteLine();
|
|
globalDeclaration.WriteTexture(taskData.uniformMapper);
|
|
globalDeclaration.WriteAttributeStruct(allCurrentAttributes.Select(a => a.attrib), "VFXAttributes");
|
|
globalDeclaration.WriteLine();
|
|
globalDeclaration.WriteAttributeStruct(allSourceAttributes.Select(a => a.attrib), "VFXSourceAttributes");
|
|
globalDeclaration.WriteLine();
|
|
|
|
globalDeclaration.WriteEventBuffers(eventListOutName, taskData.linkedEventOut.Length);
|
|
|
|
var expressionToName = BuildExpressionToName(context, taskData);
|
|
BuildContextBlocks(context, taskData, expressionToName, out var blockFunction, out var blockCallFunction, out var blockIncludes, out var blockDefines);
|
|
|
|
//< Final composition
|
|
var globalIncludeContent = new VFXShaderWriter();
|
|
|
|
if (enableShaderDebugSymbols)
|
|
{
|
|
globalIncludeContent.WriteLine("#pragma enable_d3d11_debug_symbols");
|
|
globalIncludeContent.WriteLine();
|
|
}
|
|
|
|
globalIncludeContent.WriteLine("#define NB_THREADS_PER_GROUP " + nbThreadsPerGroup);
|
|
globalIncludeContent.WriteLine("#define HAS_VFX_ATTRIBUTES 1");
|
|
globalIncludeContent.WriteLine("#define VFX_PASSDEPTH_ACTUAL (0)");
|
|
globalIncludeContent.WriteLine("#define VFX_PASSDEPTH_MOTION_VECTOR (1)");
|
|
globalIncludeContent.WriteLine("#define VFX_PASSDEPTH_SELECTION (2)");
|
|
globalIncludeContent.WriteLine("#define VFX_PASSDEPTH_PICKING (3)");
|
|
globalIncludeContent.WriteLine("#define VFX_PASSDEPTH_SHADOW (4)");
|
|
|
|
foreach (var attribute in allCurrentAttributes)
|
|
globalIncludeContent.WriteLineFormat("#define VFX_USE_{0}_{1} 1", attribute.attrib.name.ToUpper(CultureInfo.InvariantCulture), "CURRENT");
|
|
foreach (var attribute in allSourceAttributes)
|
|
globalIncludeContent.WriteLineFormat("#define VFX_USE_{0}_{1} 1", attribute.attrib.name.ToUpper(CultureInfo.InvariantCulture), "SOURCE");
|
|
|
|
foreach (var additionnalHeader in context.additionalDataHeaders)
|
|
globalIncludeContent.WriteLine(additionnalHeader);
|
|
|
|
foreach (var additionnalDefine in allAdditionalDefines)
|
|
globalIncludeContent.WriteLineFormat("#define {0}{1}", additionnalDefine, additionnalDefine.Contains(' ') ? "" : " 1");
|
|
|
|
// We consider that tasks are always generating a compute shader.
|
|
bool generateComputes = task.shaderType == VFXTaskShaderType.ComputeShader;
|
|
|
|
var renderTemplatePipePath = VFXLibrary.currentSRPBinder.templatePath;
|
|
var renderRuntimePipePath = VFXLibrary.currentSRPBinder.runtimePath;
|
|
if (!generateComputes && !string.IsNullOrEmpty(renderTemplatePipePath))
|
|
{
|
|
string renderPipePasses = renderTemplatePipePath + "/VFXPasses.template";
|
|
globalIncludeContent.Write(GetFlattenedTemplateContent(renderPipePasses, new List<string>(), allAdditionalDefines, dependencies));
|
|
}
|
|
|
|
if (contextData is ISpaceable)
|
|
{
|
|
var spaceable = contextData as ISpaceable;
|
|
globalIncludeContent.WriteLineFormat("#define {0} 1", spaceable.space == VFXSpace.World ? "VFX_WORLD_SPACE" : "VFX_LOCAL_SPACE");
|
|
}
|
|
globalIncludeContent.WriteLineFormat("#include_with_pragmas \"{0}/VFXDefines.hlsl\"", renderRuntimePipePath);
|
|
|
|
if (needsGraphValueStruct)
|
|
globalIncludeContent.WriteLine("#define VFX_USE_GRAPH_VALUES 1");
|
|
|
|
foreach (string s in GetInstancingAdditionalDefines(context, task.type, particleData))
|
|
globalIncludeContent.WriteLine(s);
|
|
|
|
var perPassIncludeContent = new VFXShaderWriter();
|
|
string renderPipeCommon = context.doesIncludeCommonCompute ? "Packages/com.unity.visualeffectgraph/Shaders/Common/VFXCommonCompute.hlsl" : renderRuntimePipePath + "/VFXCommon.hlsl";
|
|
perPassIncludeContent.WriteLine("#include \"" + renderPipeCommon + "\"");
|
|
perPassIncludeContent.WriteLine("#include \"Packages/com.unity.visualeffectgraph/Shaders/VFXCommon.hlsl\"");
|
|
if (!generateComputes)
|
|
{
|
|
perPassIncludeContent.WriteLine("#include \"Packages/com.unity.visualeffectgraph/Shaders/VFXCommonOutput.hlsl\"");
|
|
}
|
|
globalIncludeContent.Write(blockDefines.builder.ToString());
|
|
perPassIncludeContent.Write(blockIncludes.builder.ToString());
|
|
|
|
ReplaceMultiline(stringBuilder, "${VFXGlobalInclude}", globalIncludeContent.builder);
|
|
ReplaceMultiline(stringBuilder, "${VFXGlobalDeclaration}", globalDeclaration.builder);
|
|
ReplaceMultiline(stringBuilder, "${VFXPerPassInclude}", perPassIncludeContent.builder);
|
|
ReplaceMultiline(stringBuilder, "${VFXGeneratedBlockFunction}", blockFunction.builder);
|
|
ReplaceMultiline(stringBuilder, "${VFXProcessBlocks}", blockCallFunction.builder);
|
|
|
|
VFXShaderWriter fillGraphValueStruct = new VFXShaderWriter();
|
|
fillGraphValueStruct.GenerateFillGraphValuesStruct(taskData.uniformMapper, particleData.graphValuesLayout);
|
|
ReplaceMultiline(stringBuilder, "${VFXLoadGraphValues}", fillGraphValueStruct.builder);
|
|
|
|
VFXShaderWriter loadContextData = new VFXShaderWriter();
|
|
loadContextData.GenerateLoadContextData(particleData.graphValuesLayout);
|
|
ReplaceMultiline(stringBuilder, "${VFXLoadContextData}", loadContextData.builder);
|
|
|
|
var mainParameters = taskData.gpuMapper.CollectExpression(-1).ToArray();
|
|
foreach (var match in GetUniqueMatches("\\${VFXLoadParameter:{(.*?)}}", stringBuilder.ToString()))
|
|
{
|
|
var str = match.Groups[0].Value;
|
|
var pattern = match.Groups[1].Value;
|
|
var loadParameters = GenerateLoadParameter(pattern, mainParameters, expressionToName);
|
|
ReplaceMultiline(stringBuilder, str, loadParameters.builder);
|
|
}
|
|
|
|
// Old SG integration
|
|
VFXOldShaderGraphHelpers.ReplaceShaderGraphTag(stringBuilder, context, mainParameters, expressionToName);
|
|
|
|
//< Load Attribute
|
|
if (stringBuilder.ToString().Contains("${VFXLoadAttributes}"))
|
|
{
|
|
var loadAttributes = GenerateLoadAttribute(".*", context, taskData);
|
|
ReplaceMultiline(stringBuilder, "${VFXLoadAttributes}", loadAttributes.builder);
|
|
}
|
|
|
|
foreach (var match in GetUniqueMatches("\\${VFXLoadAttributes:{(.*?)}}", stringBuilder.ToString()))
|
|
{
|
|
var str = match.Groups[0].Value;
|
|
var pattern = match.Groups[1].Value;
|
|
var loadAttributes = GenerateLoadAttribute(pattern, context, taskData);
|
|
ReplaceMultiline(stringBuilder, str, loadAttributes.builder);
|
|
}
|
|
|
|
//< Store Attribute
|
|
if (stringBuilder.ToString().Contains("${VFXStoreAttributes}"))
|
|
{
|
|
var storeAttribute = GenerateStoreAttribute(".*", context, (uint)taskData.linkedEventOut.Length);
|
|
ReplaceMultiline(stringBuilder, "${VFXStoreAttributes}", storeAttribute.builder);
|
|
}
|
|
|
|
foreach (var match in GetUniqueMatches("\\${VFXStoreAttributes:{(.*?)}}", stringBuilder.ToString()))
|
|
{
|
|
var str = match.Groups[0].Value;
|
|
var pattern = match.Groups[1].Value;
|
|
var storeAttributes = GenerateStoreAttribute(pattern, context, (uint)taskData.linkedEventOut.Length);
|
|
ReplaceMultiline(stringBuilder, str, storeAttributes.builder);
|
|
}
|
|
|
|
//< Detect needed pragma require
|
|
var useCubeArray = taskData.uniformMapper.textures.Any(o => o.valueType == VFXValueType.TextureCubeArray);
|
|
var pragmaRequire = useCubeArray ? new StringBuilder("#pragma require cubearray") : new StringBuilder();
|
|
ReplaceMultiline(stringBuilder, "${VFXPragmaRequire}", pragmaRequire);
|
|
if (VFXLibrary.currentSRPBinder != null)
|
|
{
|
|
var allowedRenderers = new StringBuilder("#pragma only_renderers ");
|
|
allowedRenderers.Append(String.Join(" ", VFXLibrary.currentSRPBinder.GetSupportedGraphicDevices().Select(d => DeviceTypeToShaderString(d))));
|
|
ReplaceMultiline(stringBuilder, "${VFXPragmaOnlyRenderers}", allowedRenderers);
|
|
}
|
|
|
|
foreach (var addionalReplacement in context.additionalReplacements)
|
|
{
|
|
ReplaceMultiline(stringBuilder, addionalReplacement.Key, addionalReplacement.Value.builder);
|
|
}
|
|
|
|
// Replace defines
|
|
SubstituteMacros(stringBuilder);
|
|
|
|
if (VFXViewPreference.advancedLogs)
|
|
Debug.LogFormat("GENERATED_OUTPUT_FILE_FOR : {0}\n{1}", context.ToString(), stringBuilder.ToString());
|
|
|
|
context.EndCompilation();
|
|
Profiler.EndSample();
|
|
return stringBuilder;
|
|
}
|
|
|
|
static string DeviceTypeToShaderString(GraphicsDeviceType deviceType) => deviceType switch
|
|
{
|
|
GraphicsDeviceType.Direct3D11 => "d3d11",
|
|
GraphicsDeviceType.OpenGLCore => "glcore",
|
|
GraphicsDeviceType.OpenGLES3 => "gles3",
|
|
GraphicsDeviceType.Metal => "metal",
|
|
GraphicsDeviceType.Vulkan => "vulkan",
|
|
GraphicsDeviceType.XboxOne => "xboxone",
|
|
GraphicsDeviceType.GameCoreXboxOne => "xboxone",
|
|
GraphicsDeviceType.GameCoreXboxSeries => "xboxseries",
|
|
GraphicsDeviceType.PlayStation4 => "playstation",
|
|
GraphicsDeviceType.Switch => "switch",
|
|
GraphicsDeviceType.PlayStation5 => "ps5",
|
|
GraphicsDeviceType.WebGPU => "webgpu",
|
|
_ => throw new Exception($"Graphics Device Type '{deviceType}' not supported in shader string."),
|
|
};
|
|
|
|
private static StringBuilder TryBuildFromShaderGraph(VFXContext context, VFXTaskCompiledData taskData, out List<string> errors)
|
|
{
|
|
errors = null;
|
|
var stringBuilder = new StringBuilder();
|
|
|
|
// Reconstruct the ShaderGraph.
|
|
var path = AssetDatabase.GetAssetPath(VFXShaderGraphHelpers.GetShaderGraph(context));
|
|
|
|
AssetCollection assetCollection = new AssetCollection();
|
|
MinimalGraphData.GatherMinimalDependenciesFromFile(path, assetCollection);
|
|
|
|
var textGraph = File.ReadAllText(path, Encoding.UTF8);
|
|
var graph = new GraphData
|
|
{
|
|
messageManager = new MessageManager(),
|
|
assetGuid = AssetDatabase.AssetPathToGUID(path)
|
|
};
|
|
MultiJson.Deserialize(graph, textGraph);
|
|
graph.OnEnable();
|
|
graph.ValidateGraph();
|
|
|
|
// Check the validity of the shader graph (unsupported keywords or shader property usage).
|
|
if (VFXLibrary.currentSRPBinder == null || !VFXLibrary.currentSRPBinder.CheckGraphDataValid(graph, out errors))
|
|
return stringBuilder;
|
|
|
|
var target = graph.activeTargets.Where(o =>
|
|
{
|
|
if (o.SupportsVFX())
|
|
{
|
|
//We are assuming the target has been implemented in the same package than srp binder.
|
|
var srpBinderAssembly = VFXLibrary.currentSRPBinder.GetType().Assembly;
|
|
var targetAssembly = o.GetType().Assembly;
|
|
if (srpBinderAssembly == targetAssembly)
|
|
return true;
|
|
}
|
|
return false;
|
|
}).FirstOrDefault();
|
|
|
|
if (target == null || !target.TryConfigureContextData(context, taskData))
|
|
return stringBuilder; //If TryConfigureContextData failed, it would be nice to fallback to the error feedback (done with https://github.cds.internal.unity3d.com/unity/unity/pull/8564)
|
|
|
|
//Remove multi_compile which are going to be constant folded
|
|
if (taskData.SGInputs != null)
|
|
{
|
|
foreach (var keyword in graph.keywords)
|
|
{
|
|
if (taskData.SGInputs.IsPredefinedKeyword(keyword.referenceName))
|
|
{
|
|
keyword.keywordDefinition = KeywordDefinition.Predefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use ShaderGraph to generate the VFX shader.
|
|
var text = ShaderGraphImporter.GetShaderText(path, out var configuredTextures, assetCollection, graph, GenerationMode.VFX, new[] { target });
|
|
|
|
// Append the shader + strip the name header (VFX stamps one in later on).
|
|
stringBuilder.Append(text);
|
|
stringBuilder.Remove(0, text.IndexOf("{", StringComparison.Ordinal));
|
|
|
|
return stringBuilder;
|
|
}
|
|
|
|
private static void BuildBlock(VFXTaskCompiledData taskData, VFXShaderWriter blockFunction, VFXShaderWriter blockCallFunction, HashSet<string> blockDeclared, Dictionary<VFXExpression, string> expressionToName, VFXBlock block, ref int blockIndex)
|
|
{
|
|
// Check enabled state
|
|
VFXExpression enabledExp = taskData.gpuMapper.FromNameAndId(VFXBlock.activationSlotName, blockIndex);
|
|
bool needsEnabledCheck = enabledExp != null && !enabledExp.Is(VFXExpression.Flags.Constant);
|
|
if (enabledExp != null && !needsEnabledCheck && !enabledExp.Get<bool>())
|
|
throw new ArgumentException("This method should not be called on a disabled block");
|
|
|
|
var parameters = block.mergedAttributes.Select(o => new VFXShaderWriter.FunctionParameter
|
|
{
|
|
name = o.attrib.name,
|
|
expression = new VFXAttributeExpression(o.attrib),
|
|
mode = o.mode
|
|
}).ToList();
|
|
|
|
foreach (var parameter in block.parameters)
|
|
{
|
|
var expReduced = taskData.gpuMapper.FromNameAndId(parameter.name, blockIndex);
|
|
if (VFXExpression.IsTypeValidOnGPU(expReduced.valueType))
|
|
{
|
|
parameters.Add(new VFXShaderWriter.FunctionParameter
|
|
{
|
|
name = parameter.name,
|
|
expression = expReduced,
|
|
mode = VFXAttributeMode.None
|
|
});
|
|
}
|
|
}
|
|
|
|
string methodName, commentMethod;
|
|
GetFunctionName(block, out methodName, out commentMethod);
|
|
if (!blockDeclared.Contains(methodName))
|
|
{
|
|
blockDeclared.Add(methodName);
|
|
blockFunction.WriteBlockFunction(taskData,
|
|
methodName,
|
|
block.source,
|
|
parameters,
|
|
commentMethod);
|
|
}
|
|
|
|
var expressionToNameLocal = expressionToName;
|
|
bool needsEnabledScope = needsEnabledCheck && !expressionToNameLocal.ContainsKey(enabledExp);
|
|
bool hasParameterTransformation = parameters.Any(o => !expressionToNameLocal.ContainsKey(o.expression));
|
|
bool needsParametersScope = needsEnabledCheck || hasParameterTransformation;
|
|
|
|
if (needsEnabledScope || hasParameterTransformation)
|
|
{
|
|
expressionToNameLocal = new Dictionary<VFXExpression, string>(expressionToNameLocal);
|
|
}
|
|
|
|
if (needsEnabledScope)
|
|
{
|
|
blockCallFunction.EnterScope();
|
|
blockCallFunction.WriteVariable(enabledExp, expressionToNameLocal);
|
|
}
|
|
|
|
if (needsEnabledCheck)
|
|
{
|
|
blockCallFunction.WriteLineFormat("if ({0})", expressionToNameLocal[enabledExp]);
|
|
}
|
|
|
|
if (needsParametersScope)
|
|
{
|
|
blockCallFunction.EnterScope();
|
|
foreach (var exp in parameters.Select(o => o.expression))
|
|
{
|
|
if (expressionToNameLocal.ContainsKey(exp))
|
|
continue;
|
|
blockCallFunction.WriteVariable(exp, expressionToNameLocal);
|
|
}
|
|
}
|
|
|
|
var indexEventCount = parameters.FindIndex(o => o.name == VFXAttribute.EventCount.name);
|
|
if (indexEventCount != -1)
|
|
{
|
|
if ((parameters[indexEventCount].mode & VFXAttributeMode.Read) != 0)
|
|
throw new InvalidOperationException(string.Format("{0} isn't expected as read (special case)", VFXAttribute.EventCount.name));
|
|
blockCallFunction.WriteLineFormat("{0} = 0u;", VFXAttribute.EventCount.GetNameInCode(VFXAttributeLocation.Current));
|
|
}
|
|
|
|
blockCallFunction.WriteCallFunction(methodName,
|
|
parameters,
|
|
taskData.gpuMapper,
|
|
expressionToNameLocal);
|
|
|
|
if (indexEventCount != -1)
|
|
{
|
|
foreach (var outputSlot in block.outputSlots.SelectMany(o => o.LinkedSlots))
|
|
{
|
|
var eventIndex = Array.FindIndex(taskData.linkedEventOut, o => o.slot == outputSlot);
|
|
if (eventIndex != -1)
|
|
blockCallFunction.WriteLineFormat("{0}_{1} += {2};", VFXAttribute.EventCount.name, VFXCodeGeneratorHelper.GeneratePrefix((uint)eventIndex), VFXAttribute.EventCount.GetNameInCode(VFXAttributeLocation.Current));
|
|
}
|
|
}
|
|
|
|
if (needsParametersScope)
|
|
blockCallFunction.ExitScope();
|
|
|
|
if (needsEnabledScope)
|
|
blockCallFunction.ExitScope();
|
|
|
|
blockIndex++;
|
|
}
|
|
|
|
internal static IEnumerable<string> GetInstancingAdditionalDefines(VFXContext context, VFXTaskType taskType, VFXDataParticle particleData)
|
|
{
|
|
yield return "#define VFX_USE_INSTANCING 1";
|
|
|
|
bool isOutputTask = (taskType & VFXTaskType.Output) != 0;
|
|
if (context is VFXAbstractParticleOutput output && isOutputTask)
|
|
{
|
|
uint fixedSize;
|
|
if (!output.IsInstancingFixedSize(out fixedSize))
|
|
{
|
|
fixedSize = particleData.alignedCapacity;
|
|
}
|
|
yield return "#define VFX_INSTANCING_FIXED_SIZE " + fixedSize;
|
|
yield return "#pragma multi_compile_instancing";
|
|
}
|
|
else if (context is VFXBasicInitialize)
|
|
{
|
|
yield return "#define VFX_INSTANCING_VARIABLE_SIZE 1";
|
|
}
|
|
else
|
|
{
|
|
if (particleData.IsAttributeStored(VFXAttribute.Alive))
|
|
{
|
|
yield return "#define VFX_INSTANCING_FIXED_SIZE " + Math.Max(particleData.alignedCapacity, nbThreadsPerGroup);
|
|
}
|
|
else
|
|
{
|
|
yield return "#define VFX_INSTANCING_VARIABLE_SIZE 1";
|
|
}
|
|
}
|
|
|
|
bool hasActiveIndirection = context.contextType != VFXContextType.Output;
|
|
// TODO: how can we know if there are variable expressions with textures/buffers?
|
|
if (hasActiveIndirection)
|
|
yield return "#define VFX_INSTANCING_ACTIVE_INDIRECTION 1";
|
|
|
|
bool hasBatchIndirection = context.contextType != VFXContextType.Output;
|
|
if (hasBatchIndirection)
|
|
yield return "#define VFX_INSTANCING_BATCH_INDIRECTION 1";
|
|
}
|
|
}
|
|
}
|