using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEditor.VFX; using UnityEngine.VFX; using Type = System.Type; using Object = UnityEngine.Object; namespace UnityEditor.VFX { [Flags] enum VFXContextType { None = 0, Spawner = 1 << 0, Init = 1 << 1, OutputEvent = 1 << 2, Update = 1 << 3, Output = 1 << 4, Event = 1 << 5, SpawnerGPU = 1 << 6, Subgraph = 1 << 7, Filter = 1 << 8, InitAndUpdate = Init | Update, InitAndUpdateAndOutput = Init | Update | Output, UpdateAndOutput = Update | Output, All = Init | Update | Output | Spawner | Subgraph, CanHaveBlocks = ~(OutputEvent | Event | SpawnerGPU), }; [Flags] enum VFXDataType { None = 0, SpawnEvent = 1 << 0, OutputEvent = 1 << 1, Particle = 1 << 2, Mesh = 1 << 3, ParticleStrip = 1 << 4 | Particle, // strips }; [Serializable] struct VFXContextLink { public VFXContext context; public int slotIndex; } [Serializable] class VFXContextSlot { public List link = new List(); } internal class VFXContext : VFXSlotContainerModel, IVFXDataGetter { public const int kMaxFlowCount = 10; protected static string RenderPipeTemplate(string fileName) { return VFXLibrary.currentSRPBinder.templatePath + "/Templates/" + fileName; } [SerializeField] private string m_Label; public string label { get { return m_Label; } set { if (m_Label != value) { m_Label = value; Invalidate(InvalidationCause.kSettingChanged); } } } private VFXContext() { m_UICollapsed = false; } // Used by serialization public VFXContext(VFXContextType contextType, VFXDataType inputType, VFXDataType outputType) { m_ContextType = contextType; m_InputType = inputType; m_OutputType = outputType; } public VFXContext(VFXContextType contextType) : this(contextType, VFXDataType.None, VFXDataType.None) { } // Called by VFXData public static T CreateImplicitContext(VFXData data) where T : VFXContext { var context = ScriptableObject.CreateInstance(); context.m_Data = data; return context; } public override void OnEnable() { int nbRemoved = 0; if (m_InputFlowSlot == null) m_InputFlowSlot = Enumerable.Range(0, inputFlowCount).Select(_ => new VFXContextSlot()).ToArray(); else for (int i = 0; i < m_InputFlowSlot.Length; ++i) nbRemoved += m_InputFlowSlot[i].link.RemoveAll(s => s.context == null); if (m_OutputFlowSlot == null) m_OutputFlowSlot = Enumerable.Range(0, outputFlowCount).Select(_ => new VFXContextSlot()).ToArray(); else for (int i = 0; i < m_OutputFlowSlot.Length; ++i) nbRemoved += m_OutputFlowSlot[i].link.RemoveAll(s => s.context == null); if (nbRemoved > 0) Debug.LogWarningFormat("Remove {0} linked context(s) that could not be deserialized from {1} of type {2}", nbRemoved, name, GetType()); if (m_Data == null) SetDefaultData(false); m_UICollapsed = false; base.OnEnable(); } public virtual bool doesGenerateShader { get { return codeGeneratorTemplate != null; } } public virtual string codeGeneratorTemplate { get { return null; } } public virtual bool codeGeneratorCompute { get { return true; } } public virtual bool doesIncludeCommonCompute { get { return codeGeneratorCompute; } } public virtual VFXContextType contextType { get { return m_ContextType; } } public virtual VFXDataType inputType { get { return m_InputType; } } public virtual VFXDataType outputType { get { return m_OutputType; } } public virtual VFXDataType ownedType { get { return contextType == VFXContextType.Output ? inputType : outputType; } } public virtual VFXTaskType taskType { get { return VFXTaskType.None; } } public virtual IEnumerable attributes { get { return Enumerable.Empty(); } } public virtual IEnumerable additionalMappings { get { return Enumerable.Empty(); } } public virtual IEnumerable additionalDataHeaders { get { return GetData().additionalHeaders; } } public virtual IEnumerable additionalDefines { get { return Enumerable.Empty(); } } public virtual IEnumerable> additionalReplacements { get { return Enumerable.Empty>(); } } public virtual IEnumerable fragmentParameters { get { return Enumerable.Empty(); } } public virtual bool usesMaterialVariantInEditMode { get { return false; } } public virtual VFXContextCompiledData PrepareCompiledData() { var compiledData = new VFXContextCompiledData { tasks = new() { new VFXTask { doesGenerateShader = doesGenerateShader, templatePath = codeGeneratorTemplate, type = taskType, shaderType = codeGeneratorCompute ? VFXTaskShaderType.ComputeShader : VFXTaskShaderType.Shader, } }, buffers = new() }; return compiledData; } public virtual bool CanBeCompiled() { return m_Data != null && m_Data.CanBeCompiled(); } public void MarkAsCompiled(bool compiled) { hasBeenCompiled = compiled; } public override void CollectDependencies(HashSet objs, bool ownedOnly = true) { base.CollectDependencies(objs, ownedOnly); if (m_Data != null) { objs.Add(m_Data); m_Data.CollectDependencies(objs, ownedOnly); } } private static bool IsDisabledBlock(VFXModel model) { VFXBlock block = null; if (model is VFXBlock) block = (VFXBlock)model; else if (model is VFXSlot) block = ((VFXSlot)model).owner as VFXBlock; return block != null && !block.enabled; } protected override void OnInvalidate(VFXModel model, InvalidationCause cause) { base.OnInvalidate(model, cause); if (cause == InvalidationCause.kStructureChanged || cause == InvalidationCause.kConnectionChanged || cause == InvalidationCause.kExpressionInvalidated || cause == InvalidationCause.kSettingChanged || cause == InvalidationCause.kEnableChanged) { if (hasBeenCompiled || CanBeCompiled()) { bool skip = false; // Check if the invalidation comes from a disable block and in that case don't recompile if (cause != InvalidationCause.kEnableChanged) { skip = IsDisabledBlock(model); } if (!skip) Invalidate(InvalidationCause.kExpressionGraphChanged); } } if (cause == InvalidationCause.kParamChanged || cause == InvalidationCause.kExpressionValueInvalidated) { if (contextType == VFXContextType.Spawner || contextType == VFXContextType.Init) { bool isBounds = model is VFXSlot slot && ( slot.name.Equals(nameof(VFXBasicInitialize.InputPropertiesBounds.bounds)) || slot.name.Equals(nameof(VFXBasicInitialize.InputPropertiesPadding.boundsPadding))); // Skip bounds so no reinit occurs when authoring the bounds if ((hasBeenCompiled || CanBeCompiled()) && !IsDisabledBlock(model) && !isBounds) Invalidate(InvalidationCause.kInitValueChanged); } } } public virtual bool SetupCompilation() { return true; } public virtual void EndCompilation() { } public void DetachAllInputFlowSlots(bool notify = true) { //Unlink all existing links. It is up to the user of this method to backup and restore links. if (m_InputFlowSlot != null) { for (int slot = 0; slot < m_InputFlowSlot.Length; slot++) { while (m_InputFlowSlot[slot].link.Count > 0) { var clean = m_InputFlowSlot[slot].link.Last(); InnerUnlink(clean.context, this, clean.slotIndex, slot, notify); } } } m_InputFlowSlot = Enumerable.Range(0, inputFlowCount).Select(_ => new VFXContextSlot()).ToArray(); } public override bool AcceptChild(VFXModel model, int index = -1) { if (!base.AcceptChild(model, index)) return false; var block = (VFXBlock)model; return Accept(block, index); } public virtual bool Accept(VFXBlock block, int index = -1) { var testedType = contextType == VFXContextType.Output ? inputType : outputType; return ((block.compatibleContexts & contextType) != 0) && ((block.compatibleData & testedType) != 0); } public bool CanHaveBlocks() { return (contextType & VFXContextType.CanHaveBlocks) != 0; } protected override void OnAdded() { base.OnAdded(); if (hasBeenCompiled || CanBeCompiled()) Invalidate(InvalidationCause.kExpressionGraphChanged); } protected override void OnRemoved() { base.OnRemoved(); if (hasBeenCompiled || CanBeCompiled()) Invalidate(InvalidationCause.kExpressionGraphChanged); } public static bool CanLink(VFXContext from, VFXContext to, int fromIndex = 0, int toIndex = 0) { if (from == to || from == null || to == null) return false; if (from.outputType == VFXDataType.None || to.inputType == VFXDataType.None || (from.outputType & to.inputType) != to.inputType) return false; if (fromIndex >= from.outputFlowSlot.Length || toIndex >= to.inputFlowSlot.Length) return false; //If link already present, returns false if (from.m_OutputFlowSlot[fromIndex].link.Any(o => o.context == to && o.slotIndex == toIndex) || to.m_InputFlowSlot[toIndex].link.Any(o => o.context == from && o.slotIndex == fromIndex)) return false; //Special incorrect case, GPUEvent use the same type than Spawner which leads to an unexpected allowed link on output & spawner. if (from.m_ContextType == VFXContextType.SpawnerGPU && to.m_ContextType != VFXContextType.Init) return false; //If we want to prevent no mixing of GPUEvent & Spawn Context on Initialize. (allowed but disconnect invalid link) /*if (to.m_ContextType == VFXContextType.Init) { var currentSlot = to.m_InputFlowSlot.SelectMany(o => o.link).Select(o => o.context.m_ContextType).FirstOrDefault(); if (currentSlot != VFXContextType.None && currentSlot != from.m_ContextType) return false; }*/ //Can't connect directly event to context to OutputEvent if (from.m_ContextType == VFXContextType.Event && to.contextType == VFXContextType.OutputEvent) return false; return true; } public void LinkTo(VFXContext context, int fromIndex = 0, int toIndex = 0) { InnerLink(this, context, fromIndex, toIndex); } public void LinkFrom(VFXContext context, int fromIndex = 0, int toIndex = 0) { InnerLink(context, this, fromIndex, toIndex); } public void UnlinkTo(VFXContext context, int fromIndex = 0, int toIndex = 0) { InnerUnlink(this, context, fromIndex, toIndex); } public void UnlinkFrom(VFXContext context, int fromIndex = 0, int toIndex = 0) { InnerUnlink(context, this, fromIndex, toIndex); } public void UnlinkAll() { for (int slot = 0; slot < m_OutputFlowSlot.Length; slot++) { while (m_OutputFlowSlot[slot].link.Count > 0) { var clean = m_OutputFlowSlot[slot].link.Last(); InnerUnlink(this, clean.context, slot, clean.slotIndex); } } for (int slot = 0; slot < m_InputFlowSlot.Length; slot++) { while (m_InputFlowSlot[slot].link.Count > 0) { var clean = m_InputFlowSlot[slot].link.Last(); InnerUnlink(clean.context, this, clean.slotIndex, slot); } } } private bool CanLinkToMany() { return contextType == VFXContextType.Spawner || contextType == VFXContextType.Event; } private bool CanLinkFromMany() { return contextType == VFXContextType.Output || contextType == VFXContextType.OutputEvent || contextType == VFXContextType.Spawner || contextType == VFXContextType.Subgraph || contextType == VFXContextType.Init; } private static bool CanMixingFrom(VFXContextType from, VFXContextType to, VFXContextType lastFavoriteTo) { if (from == VFXContextType.Init || from == VFXContextType.Update) { if (lastFavoriteTo == VFXContextType.Update) return to == VFXContextType.Update; if (lastFavoriteTo == VFXContextType.Output) return to == VFXContextType.Output; } //No special case outside init output which can't be mixed with output & update return true; } private static bool CanMixingTo(VFXContextType from, VFXContextType to, VFXContextType lastFavoriteFrom) { if (to == VFXContextType.Init) { //Init is exclusive either {event, spawner} xor {spawnerGPU}, not both if (lastFavoriteFrom == VFXContextType.Event || lastFavoriteFrom == VFXContextType.Spawner) return from == VFXContextType.Event || from == VFXContextType.Spawner; if (lastFavoriteFrom == VFXContextType.SpawnerGPU) return from == VFXContextType.SpawnerGPU; } else if (to == VFXContextType.Spawner || to == VFXContextType.OutputEvent) { //No special constraint on spawner or output event (gpuEvent isn't allowed anyway) return true; } //Default case, type transfer aren't expected return from == to && to == lastFavoriteFrom; } protected static void InnerLink(VFXContext from, VFXContext to, int fromIndex, int toIndex, bool notify = true) { if (!CanLink(from, to, fromIndex, toIndex)) throw new ArgumentException(string.Format("Cannot link contexts {0} and {1}", from, to)); // Handle constraints on connections foreach (var link in from.m_OutputFlowSlot[fromIndex].link.ToArray()) { if (!link.context.CanLinkFromMany() || !CanMixingFrom(from.contextType, link.context.contextType, to.contextType)) { if (link.context.inputFlowCount > toIndex) //Special case from SubGraph, not sure how this test could be false InnerUnlink(from, link.context, fromIndex, toIndex, notify); } } foreach (var link in to.m_InputFlowSlot[toIndex].link.ToArray()) { if (!link.context.CanLinkToMany() || !CanMixingTo(link.context.contextType, to.contextType, from.contextType)) { InnerUnlink(link.context, to, fromIndex, toIndex, notify); } } if ((from.ownedType & to.ownedType) == to.ownedType && from.ownedType.HasFlag(VFXDataType.Particle)) to.InnerSetData(from.GetData(), false); from.m_OutputFlowSlot[fromIndex].link.Add(new VFXContextLink() { context = to, slotIndex = toIndex }); to.m_InputFlowSlot[toIndex].link.Add(new VFXContextLink() { context = from, slotIndex = fromIndex }); if (notify) { from.Invalidate(InvalidationCause.kConnectionChanged); to.Invalidate(InvalidationCause.kConnectionChanged); } } private static void InnerUnlink(VFXContext from, VFXContext to, int fromIndex = 0, int toIndex = 0, bool notify = true) { if (from.GetData() == to.GetData() && from.GetData() != null) to.SetDefaultData(false); int count = from.m_OutputFlowSlot[fromIndex].link.RemoveAll(o => o.context == to && o.slotIndex == toIndex); count += to.m_InputFlowSlot[toIndex].link.RemoveAll(o => o.context == from && o.slotIndex == fromIndex); if (count > 0 && notify) { from.Invalidate(InvalidationCause.kConnectionChanged); to.Invalidate(InvalidationCause.kConnectionChanged); } } public VFXContextSlot[] inputFlowSlot { get { return m_InputFlowSlot == null ? new VFXContextSlot[] { } : m_InputFlowSlot; } } public VFXContextSlot[] outputFlowSlot { get { return m_OutputFlowSlot == null ? new VFXContextSlot[] { } : m_OutputFlowSlot; } } protected virtual int inputFlowCount { get { return 1; } } protected virtual int outputFlowCount { get { return 1; } } public IEnumerable inputContexts { get { return m_InputFlowSlot.SelectMany(l => l.link.Select(o => o.context)); } } public IEnumerable outputContexts { get { return m_OutputFlowSlot.SelectMany(l => l.link.Select(o => o.context)); } } public virtual VFXExpressionMapper GetExpressionMapper(VFXDeviceTarget target) { if (target == VFXDeviceTarget.GPU) return VFXExpressionMapper.FromContext(this); return null; } public void SetDefaultData(bool notify) { InnerSetData(VFXData.CreateDataType(ownedType), notify); } public virtual void OnDataChanges(VFXData oldData, VFXData newData) { } private void InnerSetData(VFXData data, bool notify) { if (m_Data != data) { if (m_Data != null) { m_Data.OnContextRemoved(this); } OnDataChanges(m_Data, data); m_Data = data; if (m_Data != null) m_Data.OnContextAdded(this); if (notify) Invalidate(InvalidationCause.kStructureChanged); // Propagate data downwards if (ownedType.HasFlag(VFXDataType.Particle)) // Only propagate for particle type atm foreach (var output in m_OutputFlowSlot.SelectMany(o => o.link.Select(l => l.context))) if (output.ownedType == ownedType) output.InnerSetData(data, notify); } } public VFXData GetData() { return m_Data; } protected virtual IEnumerable implicitPreBlock { get { return Enumerable.Empty(); } } protected virtual IEnumerable implicitPostBlock { get { return Enumerable.Empty(); } } public IEnumerable activeChildrenWithImplicit { get { return implicitPreBlock.Concat(children).Concat(implicitPostBlock).Where(o => o.enabled); } } public IEnumerable activeFlattenedChildrenWithImplicit { get { List blocks = new List(); foreach (var ctxblk in implicitPreBlock) { if (ctxblk is VFXSubgraphBlock subgraphBlk) foreach (var blk in subgraphBlk.recursiveSubBlocks) { if (blk.enabled) blocks.Add(blk); } else { if (ctxblk.enabled) blocks.Add(ctxblk); } } foreach (var ctxblk in children) { if (ctxblk is VFXSubgraphBlock subgraphBlk) foreach (var blk in subgraphBlk.recursiveSubBlocks) { if (blk.enabled) blocks.Add(blk); } else { if (ctxblk.enabled) blocks.Add(ctxblk); } } foreach (var ctxblk in implicitPostBlock) { if (ctxblk is VFXSubgraphBlock subgraphBlk) foreach (var blk in subgraphBlk.recursiveSubBlocks) { if (blk.enabled) blocks.Add(blk); } else { if (ctxblk.enabled) blocks.Add(ctxblk); } } return blocks; } } private IEnumerable allSlotContainer { get { return activeFlattenedChildrenWithImplicit.OfType().Concat(Enumerable.Repeat(this as IVFXSlotContainer, 1)); } } public IEnumerable allLinkedOutputSlot { get { return allSlotContainer.SelectMany(o => o.outputSlots.SelectMany(s => s.LinkedSlots)); } } public IEnumerable allLinkedInputSlot { get { return allSlotContainer.SelectMany(o => o.inputSlots.SelectMany(s => s.LinkedSlots)); } } // Not serialized nor exposed private VFXContextType m_ContextType; private VFXDataType m_InputType; private VFXDataType m_OutputType; [NonSerialized] private bool hasBeenCompiled = false; [SerializeField] private VFXData m_Data; [SerializeField] private VFXContextSlot[] m_InputFlowSlot; [SerializeField] private VFXContextSlot[] m_OutputFlowSlot; public char letter { get; set; } public override VFXSpace GetOutputSpaceFromSlot(VFXSlot slot) { return space; } public virtual bool spaceable { get { return m_Data is ISpaceable; } } public VFXSpace space { get { if (spaceable) { return (m_Data as ISpaceable).space; } return VFXSpace.None; } set { if (spaceable) { (m_Data as ISpaceable).space = value; foreach (var owner in m_Data.owners) Invalidate(InvalidationCause.kSettingChanged); var allSlots = m_Data.owners.SelectMany(c => c.inputSlots.Concat(c.activeFlattenedChildrenWithImplicit.SelectMany(o => o.inputSlots))); foreach (var slot in allSlots.Where(s => s.spaceable)) slot.Invalidate(InvalidationCause.kSpaceChanged); } } } public override void CheckGraphBeforeImport() { base.CheckGraphBeforeImport(); // If the graph is reimported it can be because one of its depedency such as the subgraphs, has been changed. // blocs could be subgraph blocks. foreach (var block in children) block.CheckGraphBeforeImport(); } //TODO: Register all the contexts that have issues when transfering settings (in ConvertContext() ) protected virtual IEnumerable untransferableSettings { get { return Enumerable.Empty(); } } public bool CanTransferSetting(string settingName) { return !untransferableSettings.Contains(settingName); } public bool CanTransferSetting(VFXSetting setting) { return CanTransferSetting(setting.field.Name); } public IEnumerable GetAttributesInfos() { var attributesInfos = Enumerable.Empty(); attributesInfos = attributesInfos.Concat(attributes); foreach (var block in activeFlattenedChildrenWithImplicit) attributesInfos = attributesInfos.Concat(block.attributes); return attributesInfos; } public List GetContextTaskIndices() { return GetData().GetContextTaskIndices(this); } public List CreateInstancingSplitValues(VFXExpressionGraph expressionGraph) { List instancingSplitValues = new List(); foreach (var exp in instancingSplitCPUExpressions) { int index = expressionGraph.GetFlattenedIndex(exp); if (index >= 0) { instancingSplitValues.Add((uint)index); } } return instancingSplitValues; } public virtual IEnumerable instancingSplitCPUExpressions { get { return Enumerable.Empty(); } } } }