//#define USE_SHADER_AS_SUBASSET using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Reflection; using UnityEditor; using UnityEditor.VFX; using UnityEngine; using UnityEngine.VFX; using UnityEngine.Profiling; using UnityObject = UnityEngine.Object; namespace UnityEditor.VFX { [InitializeOnLoad] class VFXGraphPreprocessor : AssetPostprocessor { void OnPreprocessAsset() { bool isVFX = VisualEffectAssetModicationProcessor.HasVFXExtension(assetPath); if (isVFX) { VisualEffectResource resource = VisualEffectResource.GetResourceAtPath(assetPath); if (resource == null) return; VFXGraph graph = resource.graph as VFXGraph; if (graph != null) graph.SanitizeForImport(); else Debug.LogError("VisualEffectGraphResource without graph"); } } static string[] OnAddResourceDependencies(string assetPath) { VisualEffectResource resource = VisualEffectResource.GetResourceAtPath(assetPath); if (resource != null) { VFXGraph graph = resource.graph as VFXGraph; if (graph != null) return resource.GetOrCreateGraph().GetImportDependencies(); else Debug.LogError("VisualEffectGraphResource without graph"); } return null; } static void OnCompileResource(VisualEffectResource resource) { if (resource != null) { VFXGraph graph = resource.graph as VFXGraph; if (graph != null) resource.GetOrCreateGraph().CompileForImport(); else Debug.LogError("VisualEffectGraphResource without graph"); } } static VFXGraphPreprocessor() { EditorApplication.update += CheckCompilationVersion; VisualEffectResource.onAddResourceDependencies = OnAddResourceDependencies; VisualEffectResource.onCompileResource = OnCompileResource; } static void CheckCompilationVersion() { EditorApplication.update -= CheckCompilationVersion; string[] allVisualEffectAssets = AssetDatabase.FindAssets("t:VisualEffectAsset"); UnityObject vfxmanager = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/VFXManager.asset").FirstOrDefault(); SerializedObject serializedVFXManager = new SerializedObject(vfxmanager); var compiledVersionProperty = serializedVFXManager.FindProperty("m_CompiledVersion"); var runtimeVersionProperty = serializedVFXManager.FindProperty("m_RuntimeVersion"); if (compiledVersionProperty.intValue != VFXGraphCompiledData.compiledVersion || runtimeVersionProperty.intValue != VisualEffectAsset.currentRuntimeDataVersion) { compiledVersionProperty.intValue = (int)VFXGraphCompiledData.compiledVersion; runtimeVersionProperty.intValue = (int)VisualEffectAsset.currentRuntimeDataVersion; serializedVFXManager.ApplyModifiedProperties(); AssetDatabase.StartAssetEditing(); foreach (var guid in allVisualEffectAssets) { string path = AssetDatabase.GUIDToAssetPath(guid); AssetDatabase.ImportAsset(path); } AssetDatabase.StopAssetEditing(); } } } class VFXCacheManager : EditorWindow { private static List GetAllVisualEffectObjects() { var vfxObjects = new List(); var vfxObjectsGuid = AssetDatabase.FindAssets("t:VisualEffectObject"); foreach (var guid in vfxObjectsGuid) { string assetPath = AssetDatabase.GUIDToAssetPath(guid); var vfxObj = AssetDatabase.LoadAssetAtPath(assetPath); if (vfxObj != null) { vfxObjects.Add(vfxObj); } } return vfxObjects; } [MenuItem("Edit/Visual Effects/Rebuild And Save All Visual Effect Graphs", priority = 320)] public static void Build() { var vfxObjects = GetAllVisualEffectObjects(); foreach (var vfxObj in vfxObjects) { if (VFXViewPreference.advancedLogs) Debug.Log(string.Format("Recompile VFX asset: {0} ({1})", vfxObj, AssetDatabase.GetAssetPath(vfxObj))); var resource = vfxObj.GetResource(); if (resource != null) { VFXGraph graph = resource.GetOrCreateGraph(); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(graph)); EditorUtility.SetDirty(resource); } } VFXExpression.ClearCache(); AssetDatabase.SaveAssets(); } } class VisualEffectAssetModicationProcessor : UnityEditor.AssetModificationProcessor { public static bool HasVFXExtension(string filePath) { if (string.IsNullOrEmpty(filePath)) return false; return filePath.EndsWith(VisualEffectResource.Extension) || filePath.EndsWith(VisualEffectSubgraphBlock.Extension) || filePath.EndsWith(VisualEffectSubgraphOperator.Extension); } static string[] OnWillSaveAssets(string[] paths) { Profiler.BeginSample("VisualEffectAssetModicationProcessor.OnWillSaveAssets"); foreach (string path in paths.Where(t => HasVFXExtension(t))) { var vfxResource = VisualEffectResource.GetResourceAtPath(path); if (vfxResource != null) { var graph = vfxResource.GetOrCreateGraph(); vfxResource.WriteAsset(); // write asset as the AssetDatabase won't do it. } } Profiler.EndSample(); return paths; } } static class VisualEffectResourceExtensions { public static VFXGraph GetOrCreateGraph(this VisualEffectResource resource) { VFXGraph graph = resource.graph as VFXGraph; if (graph == null) { string assetPath = AssetDatabase.GetAssetPath(resource); AssetDatabase.ImportAsset(assetPath); graph = resource.GetContents().OfType().FirstOrDefault(); } if (graph == null) { graph = ScriptableObject.CreateInstance(); resource.graph = graph; graph.hideFlags |= HideFlags.HideInHierarchy; graph.visualEffectResource = resource; // in this case we must update the subassets so that the graph is added to the resource dependencies graph.UpdateSubAssets(); } graph.visualEffectResource = resource; return graph; } public static void UpdateSubAssets(this VisualEffectResource resource) { resource.GetOrCreateGraph().UpdateSubAssets(); } public static bool IsAssetEditable(this VisualEffectResource resource) { return AssetDatabase.IsOpenForEdit(resource.asset, StatusQueryOptions.UseCachedIfPossible); } } static class VisualEffectObjectExtensions { public static VisualEffectResource GetOrCreateResource(this VisualEffectObject asset) { string assetPath = AssetDatabase.GetAssetPath(asset); VisualEffectResource resource = VisualEffectResource.GetResourceAtPath(assetPath); if (resource == null && !string.IsNullOrEmpty(assetPath)) { resource = new VisualEffectResource(); resource.SetAssetPath(assetPath); } return resource; } public static VisualEffectResource GetResource(this VisualEffectObject asset) { string assetPath = AssetDatabase.GetAssetPath(asset); VisualEffectResource resource = VisualEffectResource.GetResourceAtPath(assetPath); if (resource == null && !string.IsNullOrEmpty(assetPath)) throw new NullReferenceException($"VFX resource does not exist for this asset at path: {assetPath}"); return resource; } } class VFXGraph : VFXModel { // Please add increment reason for each version below // 1: Size refactor // 2: Change some SetAttribute to spaceable slot // 3: Remove Masked from blendMode in Outputs and split feature to UseAlphaClipping // 4: TransformVector|Position|Direction & DistanceToSphere|Plane|Line have now spaceable outputs // 5: Harmonized position blocks composition: PositionAABox was the only one with Overwrite position // 6: Remove automatic strip orientation from quad strip context public static readonly int CurrentVersion = 6; public readonly VFXErrorManager errorManager = new VFXErrorManager(); public override void OnEnable() { base.OnEnable(); m_ExpressionGraphDirty = true; } public VisualEffectResource visualEffectResource { get { return m_Owner; } set { if (m_Owner != value) { m_Owner = value; m_Owner.graph = this; m_ExpressionGraphDirty = true; } } } [SerializeField] VFXUI m_UIInfos; public VFXUI UIInfos { get { if (m_UIInfos == null) { m_UIInfos = ScriptableObject.CreateInstance(); } return m_UIInfos; } } public VFXParameterInfo[] m_ParameterInfo; private VFXSystemNames m_SystemNames = new VFXSystemNames(); public VFXSystemNames systemNames { get { return m_SystemNames; } } public void BuildParameterInfo() { m_ParameterInfo = VFXParameterInfo.BuildParameterInfo(this); VisualEffectEditor.RepaintAllEditors(); } public override bool AcceptChild(VFXModel model, int index = -1) { return !(model is VFXGraph); // Can hold any model except other VFXGraph } public object Backup() { Profiler.BeginSample("VFXGraph.Backup"); var dependencies = new HashSet(); dependencies.Add(this); CollectDependencies(dependencies); var result = VFXMemorySerializer.StoreObjectsToByteArray(dependencies.ToArray(), CompressionLevel.Fastest); Profiler.EndSample(); return result; } public void Restore(object str) { Profiler.BeginSample("VFXGraph.Restore"); var scriptableObject = VFXMemorySerializer.ExtractObjects(str as byte[], false); Profiler.BeginSample("VFXGraph.Restore SendUnknownChange"); foreach (var model in scriptableObject.OfType()) { model.OnUnknownChange(); } Profiler.EndSample(); Profiler.EndSample(); m_SystemNames.Sync(this); m_ExpressionGraphDirty = true; m_ExpressionValuesDirty = true; m_DependentDirty = true; } public override void CollectDependencies(HashSet objs, bool ownedOnly = true) { Profiler.BeginSample("VFXEditor.CollectDependencies"); try { if (m_UIInfos != null) objs.Add(m_UIInfos); base.CollectDependencies(objs, ownedOnly); } finally { Profiler.EndSample(); } } public void SanitizeGraph() { if (m_GraphSanitized) return; var objs = new HashSet(); CollectDependencies(objs); foreach (var model in objs.OfType()) try { model.Sanitize(m_GraphVersion); // This can modify dependencies but newly created model are supposed safe so we dont care about retrieving new dependencies } catch (Exception e) { Debug.LogError(string.Format("Exception while sanitizing model: {0} of type {1}: {2} {3}", model.name, model.GetType(), e, e.StackTrace)); } if (m_UIInfos != null) try { m_UIInfos.Sanitize(this); } catch (Exception e) { Debug.LogError(string.Format("Exception while sanitizing VFXUI: : {0} {1}", e, e.StackTrace)); } systemNames.Sync(this); int resourceCurrentVersion = 0; // Stop using reflection after 2020.2; FieldInfo info = typeof(VisualEffectResource).GetField("CurrentVersion", BindingFlags.Static | System.Reflection.BindingFlags.Public); if (info != null) resourceCurrentVersion = (int)info.GetValue(null); if (m_ResourceVersion < resourceCurrentVersion) // Graph not up to date { if (m_ResourceVersion < 1) // Version before gradient interpreted as linear { foreach (var model in objs.OfType()) { Gradient value = (Gradient)model.value; GradientColorKey[] keys = value.colorKeys; for (int i = 0; i < keys.Length; ++i) { var colorKey = keys[i]; colorKey.color = colorKey.color.linear; keys[i] = colorKey; } value.colorKeys = keys; model.value = new Gradient(); model.value = value; } } } m_ResourceVersion = resourceCurrentVersion; m_GraphSanitized = true; m_GraphVersion = CurrentVersion; #if !CASE_1289829_HAS_BEEN_FIXED if (visualEffectResource != null && (visualEffectResource.updateMode & VFXUpdateMode.ExactFixedTimeStep) == VFXUpdateMode.ExactFixedTimeStep) { visualEffectResource.updateMode = visualEffectResource.updateMode & ~VFXUpdateMode.ExactFixedTimeStep; Debug.Log("Sanitize : Exact Fixed Time has been automatically reset to false to avoid an unexpected behavior."); } #endif UpdateSubAssets(); //Should not be necessary : force remove no more referenced object from asset } public void ClearCompileData() { m_CompiledData = null; m_ExpressionValuesDirty = true; } [SerializeField] List m_ImportDependencies; public void UpdateSubAssets() { if (visualEffectResource == null) return; Profiler.BeginSample("VFXEditor.UpdateSubAssets"); try { var currentObjects = new HashSet(); currentObjects.Add(this); CollectDependencies(currentObjects); visualEffectResource.SetContents(currentObjects.Cast().ToArray()); } catch (Exception e) { Debug.LogError(e); } finally { Profiler.EndSample(); } } protected override void OnInvalidate(VFXModel model, VFXModel.InvalidationCause cause) { if (cause == VFXModel.InvalidationCause.kStructureChanged || cause == VFXModel.InvalidationCause.kSettingChanged || cause == VFXModel.InvalidationCause.kConnectionChanged) m_SystemNames.Sync(this); base.OnInvalidate(model, cause); if (model is VFXParameter //Something changed directly on VFXParameter (e.g. exposed state boolean) || model is VFXSlot && (model as VFXSlot).owner is VFXParameter //Something changed on a slot owned by a VFXParameter (e.g. the default value) || cause == VFXModel.InvalidationCause.kStructureChanged //A VFXParameter could have been removed ) { BuildParameterInfo(); } if (cause == VFXModel.InvalidationCause.kStructureChanged) { UpdateSubAssets(); if (model == this) VFXSubgraphContext.CallOnGraphChanged(this); m_DependentDirty = true; } if (cause == VFXModel.InvalidationCause.kSettingChanged && model is VFXParameter) { VFXSubgraphContext.CallOnGraphChanged(this); m_DependentDirty = true; } if (cause != VFXModel.InvalidationCause.kExpressionInvalidated && cause != VFXModel.InvalidationCause.kExpressionGraphChanged && cause != VFXModel.InvalidationCause.kUIChangedTransient && (model.hideFlags & HideFlags.DontSave) == 0) { EditorUtility.SetDirty(this); } if (cause == VFXModel.InvalidationCause.kExpressionGraphChanged) { m_ExpressionGraphDirty = true; m_DependentDirty = true; } if (cause == VFXModel.InvalidationCause.kParamChanged) { m_ExpressionValuesDirty = true; m_DependentDirty = true; } } public uint FindReducedExpressionIndexFromSlotCPU(VFXSlot slot) { RecompileIfNeeded(false, true); return compiledData.FindReducedExpressionIndexFromSlotCPU(slot); } public void SetCompilationMode(VFXCompilationMode mode) { if (m_CompilationMode != mode) { m_CompilationMode = mode; SetExpressionGraphDirty(); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(this)); } } public void SetForceShaderValidation(bool forceShaderValidation) { if (m_ForceShaderValidation != forceShaderValidation) { m_ForceShaderValidation = forceShaderValidation; if (m_ForceShaderValidation) { SetExpressionGraphDirty(); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(this)); } } } public bool IsExpressionGraphDirty() { return m_ExpressionGraphDirty; } public void SetExpressionGraphDirty(bool dirty = true) { m_ExpressionGraphDirty = dirty; m_DependentDirty = dirty; } public void SetExpressionValueDirty() { m_ExpressionValuesDirty = true; m_DependentDirty = true; } public void BuildSubgraphDependencies() { if (m_SubgraphDependencies == null) m_SubgraphDependencies = new List(); m_SubgraphDependencies.Clear(); HashSet explored = new HashSet(); RecurseBuildDependencies(explored, children); } void RecurseBuildDependencies(HashSet explored, IEnumerable models) { foreach (var model in models) { if (model is VFXSubgraphContext) { var subgraphContext = model as VFXSubgraphContext; if (subgraphContext.subgraph != null && !explored.Contains(subgraphContext.subgraph)) { explored.Add(subgraphContext.subgraph); m_SubgraphDependencies.Add(subgraphContext.subgraph); RecurseBuildDependencies(explored, subgraphContext.subgraph.GetResource().GetOrCreateGraph().children); } } else if (model is VFXSubgraphOperator) { var subgraphOperator = model as VFXSubgraphOperator; if (subgraphOperator.subgraph != null && !explored.Contains(subgraphOperator.subgraph)) { explored.Add(subgraphOperator.subgraph); m_SubgraphDependencies.Add(subgraphOperator.subgraph); RecurseBuildDependencies(explored, subgraphOperator.subgraph.GetResource().GetOrCreateGraph().children); } } else if (model is VFXContext) { foreach (var block in (model as VFXContext).children) { if (block is VFXSubgraphBlock) { var subgraphBlock = block as VFXSubgraphBlock; if (subgraphBlock.subgraph != null && !explored.Contains(subgraphBlock.subgraph)) { explored.Add(subgraphBlock.subgraph); m_SubgraphDependencies.Add(subgraphBlock.subgraph); RecurseBuildDependencies(explored, subgraphBlock.subgraph.GetResource().GetOrCreateGraph().children); } } } } } } void RecurseSubgraphRecreateCopy(IEnumerable children) { foreach (var child in children) { if (child is VFXSubgraphContext) { var subgraphContext = child as VFXSubgraphContext; subgraphContext.RecreateCopy(); if (subgraphContext.subgraph != null) { RecurseSubgraphRecreateCopy(subgraphContext.subChildren); } } else if (child is VFXContext) { foreach (var block in child.children) { if (block is VFXSubgraphBlock) { var subgraphBlock = block as VFXSubgraphBlock; subgraphBlock.RecreateCopy(); if (subgraphBlock.subgraph != null) RecurseSubgraphRecreateCopy(subgraphBlock.subChildren); } } } else if (child is VFXSubgraphOperator operatorChild) { operatorChild.RecreateCopy(); if (operatorChild.ResyncSlots(true)) operatorChild.UpdateOutputExpressions(); } } } private void SetFlattenedParentToSubblocks() { foreach (var child in children.OfType()) foreach (var block in child.children.OfType()) block.SetSubblocksFlattenedParent(); } void RecurseSubgraphPatchInputExpression(IEnumerable children) { foreach (var child in children) { if (child is VFXSubgraphContext) { var subgraphContext = child as VFXSubgraphContext; subgraphContext.PatchInputExpressions(); } else if (child is VFXContext) { foreach (var block in child.children) { if (block is VFXSubgraphBlock) { var subgraphBlock = block as VFXSubgraphBlock; subgraphBlock.PatchInputExpressions(); } } } else if (child is VFXSubgraphOperator operatorChild) { operatorChild.ResyncSlots(false); operatorChild.UpdateOutputExpressions(); } } foreach (var child in children) { if (child is VFXSubgraphContext) { var subgraphContext = child as VFXSubgraphContext; if (subgraphContext.subgraph != null && subgraphContext.subChildren != null) RecurseSubgraphPatchInputExpression(subgraphContext.subChildren); } else if (child is VFXContext) { foreach (var block in child.children) { if (block is VFXSubgraphBlock) { var subgraphBlock = block as VFXSubgraphBlock; if (subgraphBlock.subgraph != null && subgraphBlock.subChildren != null) RecurseSubgraphPatchInputExpression(subgraphBlock.subChildren); } } } } } void SubgraphDirty(VisualEffectObject subgraph) { if (m_SubgraphDependencies != null && m_SubgraphDependencies.Contains(subgraph)) { PrepareSubgraphs(); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(this)); } } private void PrepareSubgraphs() { Profiler.BeginSample("PrepareSubgraphs"); RecurseSubgraphRecreateCopy(children); SetFlattenedParentToSubblocks(); RecurseSubgraphPatchInputExpression(children); Profiler.EndSample(); } IEnumerable GetAllGraphs() where T : VisualEffectObject { var guids = AssetDatabase.FindAssets("t:" + typeof(T).Name); foreach (var assetPath in guids.Select(t => AssetDatabase.GUIDToAssetPath(t))) { var asset = AssetDatabase.LoadAssetAtPath(assetPath); if (asset != null) { var graph = asset.GetResource().GetOrCreateGraph(); yield return graph; } } } //Explicit compile must be used if we want to force compilation even if a dependency is needed, which me must not do on a deleted library import. public static bool explicitCompile { get; set; } = false; public void SanitizeForImport() { if (!explicitCompile) { HashSet dependentAsset = new HashSet(); GetImportDependentAssets(dependentAsset); foreach (var instanceID in dependentAsset) { if (instanceID != 0 && EditorUtility.InstanceIDToObject(instanceID) == null) { return; } } } foreach (var child in children) child.CheckGraphBeforeImport(); SanitizeGraph(); } public void CompileForImport() { if (!GetResource().isSubgraph) { // Don't pursue the compile if one of the dependency is not yet loaded // which happen at first import with .pcache if (!explicitCompile) { HashSet dependentAsset = new HashSet(); GetImportDependentAssets(dependentAsset); foreach (var instanceID in dependentAsset) { if (instanceID != 0 && EditorUtility.InstanceIDToObject(instanceID) == null) { return; } } } // Graph must have been sanitized at this point by the VFXGraphPreprocessor.OnPreprocess BuildSubgraphDependencies(); PrepareSubgraphs(); compiledData.Compile(m_CompilationMode, m_ForceShaderValidation); } m_ExpressionGraphDirty = false; m_ExpressionValuesDirty = false; } public static VFXCompileErrorReporter compileReporter = null; public void RecompileIfNeeded(bool preventRecompilation = false, bool preventDependencyRecompilation = false) { SanitizeGraph(); if (!GetResource().isSubgraph) { bool considerGraphDirty = m_ExpressionGraphDirty && !preventRecompilation; if (considerGraphDirty) { BuildSubgraphDependencies(); PrepareSubgraphs(); compiledData.Compile(m_CompilationMode, m_ForceShaderValidation); } else if (m_ExpressionValuesDirty && !m_ExpressionGraphDirty) { compiledData.UpdateValues(); } if (considerGraphDirty) m_ExpressionGraphDirty = false; m_ExpressionValuesDirty = false; } else if (m_ExpressionGraphDirty && !preventRecompilation) { BuildSubgraphDependencies(); PrepareSubgraphs(); m_ExpressionGraphDirty = false; } if (!preventDependencyRecompilation && m_DependentDirty) { var obj = GetResource().visualEffectObject; foreach (var graph in GetAllGraphs()) { graph.SubgraphDirty(obj); } m_DependentDirty = false; } } private VFXGraphCompiledData compiledData { get { if (m_CompiledData == null) m_CompiledData = new VFXGraphCompiledData(this); return m_CompiledData; } } public int version { get { return m_GraphVersion; } } [SerializeField] private int m_GraphVersion = CurrentVersion; [SerializeField] private int m_ResourceVersion; [NonSerialized] private bool m_GraphSanitized = false; [NonSerialized] private bool m_ExpressionGraphDirty = true; [NonSerialized] private bool m_ExpressionValuesDirty = true; [NonSerialized] private bool m_DependentDirty = true; [NonSerialized] private VFXGraphCompiledData m_CompiledData; private VFXCompilationMode m_CompilationMode = VFXCompilationMode.Runtime; private bool m_ForceShaderValidation = false; [NonSerialized] public Action onRuntimeDataChanged; [SerializeField] private List m_SubgraphDependencies = new List(); [SerializeField] private string m_CategoryPath; public string categoryPath { get { return m_CategoryPath; } set { m_CategoryPath = value; }//TODO invalidate cache here } public ReadOnlyCollection subgraphDependencies { get { return m_SubgraphDependencies.AsReadOnly(); } } public string[] GetImportDependencies() { visualEffectResource.ClearImportDependencies(); HashSet dependentAsset = new HashSet(); GetImportDependentAssets(dependentAsset); foreach (var dep in dependentAsset) { if (dep != 0) visualEffectResource.AddImportDependency(AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(dep))); } return dependentAsset.Select(t => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(t))).Distinct().ToArray(); } private VisualEffectResource m_Owner; } }