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 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(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 dependencies, bool forceShadeDebugSymbols, out List 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 GetUniqueMatches(string regexStr, string src) { var regex = new Regex(regexStr); var matches = regex.Matches(src); return matches.Cast().GroupBy(m => m.Groups[0].Value).Select(g => g.First()); } static private StringBuilder GetFlattenedTemplateContent(string path, List includes, IEnumerable defines, HashSet 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(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(); 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 BuildExpressionToName(VFXContext context, VFXTaskCompiledData taskData) { var expressionToName = new Dictionary(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 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(); var includesProcessed = new HashSet(); var defineProcessed = new HashSet(); 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(); 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 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(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(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(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 dependencies, bool enableShaderDebugSymbols, out List 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()); var stringBuilder = GetFlattenedTemplateContent(templatePath, new List(), 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(), 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 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 blockDeclared, Dictionary 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()) 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(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 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"; } } }