using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.VFX; using UnityEngine.Profiling; using Object = UnityEngine.Object; using UnityEditor.Graphs; using System.Collections.ObjectModel; namespace UnityEditor.VFX { enum VFXDeviceTarget { CPU, GPU, } class VFXExpressionGraph { private struct ExpressionData { public int depth; public int index; } public VFXExpressionGraph() { } private void AddExpressionDataRecursively(Dictionary dst, VFXExpression exp, int depth = 0) { ExpressionData data; if (!dst.TryGetValue(exp, out data) || data.depth < depth) { data.index = -1; // Will be overridden later on data.depth = depth; dst[exp] = data; foreach (var parent in exp.parents) AddExpressionDataRecursively(dst, parent, depth + 1); } } private void CompileExpressionContext(IEnumerable contexts, VFXExpressionContextOption options, VFXDeviceTarget target) { var expressionContext = new VFXExpression.Context(options, m_GlobalEventAttributes); var contextsToExpressions = target == VFXDeviceTarget.GPU ? m_ContextsToGPUExpressions : m_ContextsToCPUExpressions; var expressionsToReduced = target == VFXDeviceTarget.GPU ? m_GPUExpressionsToReduced : m_CPUExpressionsToReduced; foreach (var context in contexts) { var mapper = context.GetExpressionMapper(target); if (mapper != null) { foreach (var exp in mapper.expressions) expressionContext.RegisterExpression(exp, context); contextsToExpressions.Add(context, mapper); } } expressionContext.Compile(); foreach (var exp in expressionContext.RegisteredExpressions) { var reduced = expressionContext.GetReduced(exp); if (expressionsToReduced.TryGetValue(exp, out var previousReducedExpression)) { if (reduced != previousReducedExpression) throw new InvalidOperationException("Unexpected diverging expression reduction"); continue; } expressionsToReduced.Add(exp, reduced); } var allReduced = expressionContext.BuildAllReduced(); m_Expressions.UnionWith(allReduced); foreach (var exp in expressionsToReduced.Values) AddExpressionDataRecursively(m_ExpressionsData, exp); if (options.HasFlag(VFXExpressionContextOption.CollectPerContextData)) { foreach (var bufferTypeUsage in expressionContext.GraphicsBufferTypeUsagePerContext) { m_BufferTypeUsagePerContext.TryAdd(bufferTypeUsage.Key, bufferTypeUsage.Value); } foreach (var hlslCodeHolder in expressionContext.hlslCodeHoldersPerContext) { m_CustomHLSLExpressionsPerContext.Add(hlslCodeHolder.Key, hlslCodeHolder.Value); } } } public void CompileExpressions(VFXGraph graph, VFXExpressionContextOption options, bool filterOutInvalidContexts = false) { var models = new HashSet(); graph.CollectDependencies(models, false); var contexts = models.OfType(); if (filterOutInvalidContexts) contexts = contexts.Where(c => c.CanBeCompiled()); CompileExpressions(contexts, options); } private static void ComputeEventAttributeDescs(List globalEventAttributes, IEnumerable contexts) { globalEventAttributes.Clear(); //SpawnCount should always be added first : spawnCount is an implicit parameter from eventAttribute globalEventAttributes.Add(new VFXLayoutElementDesc() { name = VFXAttribute.SpawnCount.name, type = VFXAttribute.SpawnCount.type }); IEnumerable globalAttribute = Enumerable.Empty(); foreach (var context in contexts.Where(o => o.contextType == VFXContextType.Spawner || o.contextType == VFXContextType.Event)) { var attributesToStoreFromOutputContext = context.outputContexts.Select(o => o.GetData()).Where(o => o != null) .SelectMany(o => o.GetAttributes().Where(a => (a.mode & VFXAttributeMode.ReadSource) != 0)); var attributesReadInSpawnContext = context.GetData().GetAttributes().Where(a => (a.mode & VFXAttributeMode.Read) != 0); var attributesInGlobal = attributesToStoreFromOutputContext.Concat(attributesReadInSpawnContext).GroupBy(o => o.attrib.name); foreach (var attribute in attributesInGlobal.Select(o => o.First())) { if (!globalEventAttributes.Any(o => o.name == attribute.attrib.name)) { globalEventAttributes.Add(new VFXLayoutElementDesc() { name = attribute.attrib.name, type = attribute.attrib.type }); } } } var structureLayoutTotalSize = (uint)globalEventAttributes.Sum(e => (long)VFXExpression.TypeToSize(e.type)); var currentLayoutSize = 0u; var listWithOffset = new List(); globalEventAttributes.ForEach(e => { e.offset.element = currentLayoutSize; e.offset.structure = structureLayoutTotalSize; currentLayoutSize += (uint)VFXExpression.TypeToSize(e.type); listWithOffset.Add(e); }); globalEventAttributes.Clear(); globalEventAttributes.AddRange(listWithOffset); } public void CompileExpressions(IEnumerable contexts, VFXExpressionContextOption options) { Profiler.BeginSample("VFXEditor.CompileExpressionGraph"); try { ComputeEventAttributeDescs(m_GlobalEventAttributes, contexts); m_Expressions.Clear(); m_FlattenedExpressions.Clear(); m_CommonExpressionCount = 0u; m_ExpressionsData.Clear(); m_ContextsToGPUExpressions.Clear(); m_ContextsToCPUExpressions.Clear(); m_GPUExpressionsToReduced.Clear(); m_CPUExpressionsToReduced.Clear(); var spawnerContexts = contexts.Where(o => o.contextType == VFXContextType.Spawner); var otherContexts = contexts.Where(o => o.contextType != VFXContextType.Spawner); CompileExpressionContext(spawnerContexts, options | VFXExpressionContextOption.PatchReadToEventAttribute, VFXDeviceTarget.CPU); CompileExpressionContext(otherContexts, options, VFXDeviceTarget.CPU); CompileExpressionContext(contexts, options | VFXExpressionContextOption.GPUDataTransformation | VFXExpressionContextOption.CollectPerContextData, VFXDeviceTarget.GPU); var sortedList = m_ExpressionsData.Where(kvp => { var exp = kvp.Key; return !exp.IsAny(VFXExpression.Flags.NotCompilableOnCPU); // remove per element expression from flattened data // TODO Remove uniform constants too }); var expressionPerSpawn = sortedList.Where(o => o.Key.Is(VFXExpression.Flags.PerSpawn)); var expressionNotPerSpawn = sortedList.Where(o => !o.Key.Is(VFXExpression.Flags.PerSpawn)); //m_FlattenedExpressions is a concatenation of [sorted all expression !PerSpawn] & [sorted all expression Per Spawn] //It's more convenient for two reasons : // - Reduces process chunk for ComputePreProcessExpressionForSpawn // - Allows to determine the maximum index of expression while processing main expression evaluation sortedList = expressionNotPerSpawn.OrderByDescending(o => o.Value.depth); sortedList = sortedList.Concat(expressionPerSpawn.OrderByDescending(o => o.Value.depth)); m_FlattenedExpressions = sortedList.Select(o => o.Key).ToList(); m_CommonExpressionCount = (uint)expressionNotPerSpawn.Count(); // update index in expression data for (int i = 0; i < m_FlattenedExpressions.Count; ++i) { var data = m_ExpressionsData[m_FlattenedExpressions[i]]; data.index = i; m_ExpressionsData[m_FlattenedExpressions[i]] = data; } if (VFXViewPreference.advancedLogs) Debug.Log(string.Format("RECOMPILE EXPRESSION GRAPH - NB EXPRESSIONS: {0} - NB CPU END EXPRESSIONS: {1} - NB GPU END EXPRESSIONS: {2}", m_Expressions.Count, m_CPUExpressionsToReduced.Count, m_GPUExpressionsToReduced.Count)); } finally { Profiler.EndSample(); } } public int GetFlattenedIndex(VFXExpression exp) { if (m_ExpressionsData.ContainsKey(exp)) return m_ExpressionsData[exp].index; return -1; } public VFXExpression GetReduced(VFXExpression exp, VFXDeviceTarget target) { VFXExpression reduced; var expressionToReduced = target == VFXDeviceTarget.GPU ? m_GPUExpressionsToReduced : m_CPUExpressionsToReduced; expressionToReduced.TryGetValue(exp, out reduced); return reduced; } public VFXExpressionMapper BuildCPUMapper(VFXContext context) { return BuildMapper(context, m_ContextsToCPUExpressions, VFXDeviceTarget.CPU); } public VFXExpressionMapper BuildGPUMapper(VFXContext context) { return BuildMapper(context, m_ContextsToGPUExpressions, VFXDeviceTarget.GPU); } public List GetAllNames(VFXExpression exp) { List names = new List(); foreach (var mapper in m_ContextsToCPUExpressions.Values.Concat(m_ContextsToGPUExpressions.Values)) { names.AddRange(mapper.GetData(exp).Select(o => o.fullName)); } return names; } private VFXExpressionMapper BuildMapper(VFXContext context, Dictionary dictionnary, VFXDeviceTarget target) { VFXExpression.Flags check = target == VFXDeviceTarget.GPU ? VFXExpression.Flags.InvalidOnGPU | VFXExpression.Flags.PerElement : VFXExpression.Flags.InvalidOnCPU; VFXExpressionMapper outMapper = new VFXExpressionMapper(); VFXExpressionMapper inMapper; dictionnary.TryGetValue(context, out inMapper); if (inMapper != null) { foreach (var exp in inMapper.expressions) { var reduced = GetReduced(exp, target); if (reduced.Is(check)) { var message = $"The expression \"{reduced.GetType().Name}\" is not valid as it have the flag: {check}"; context.GetGraph().RegisterCompileError("CompileReduceExpressionFail", message, context); throw new InvalidOperationException(message); } var mappedDataList = inMapper.GetData(exp); foreach (var mappedData in mappedDataList) outMapper.AddExpression(reduced, mappedData); } } return outMapper; } public HashSet Expressions => m_Expressions; public List FlattenedExpressions => m_FlattenedExpressions; public uint CommonExpressionCount => m_CommonExpressionCount; public Dictionary GPUExpressionsToReduced => m_GPUExpressionsToReduced; public Dictionary CPUExpressionsToReduced => m_CPUExpressionsToReduced; public IEnumerable GlobalEventAttributes => m_GlobalEventAttributes; public ReadOnlyDictionary GetBufferUsage(VFXContext context) { if (m_BufferTypeUsagePerContext.TryGetValue(context, out var bufferTypeUsage)) { return new ReadOnlyDictionary(bufferTypeUsage); } return new ReadOnlyDictionary(new Dictionary()); } public IHLSLCodeHolder[] GetCustomHLSLExpressions(VFXContext context) { if (m_CustomHLSLExpressionsPerContext.TryGetValue(context, out var hlslCodeHolders)) { return hlslCodeHolders.ToArray(); } return Array.Empty(); } private Dictionary> m_CustomHLSLExpressionsPerContext = new(); private Dictionary> m_BufferTypeUsagePerContext = new(); private HashSet m_Expressions = new HashSet(); private Dictionary m_CPUExpressionsToReduced = new Dictionary(); private Dictionary m_GPUExpressionsToReduced = new Dictionary(); private List m_FlattenedExpressions = new List(); private uint m_CommonExpressionCount; private Dictionary m_ExpressionsData = new Dictionary(); private Dictionary m_ContextsToCPUExpressions = new Dictionary(); private Dictionary m_ContextsToGPUExpressions = new Dictionary(); private List m_GlobalEventAttributes = new List(); } }