You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1546 lines
60 KiB

//#define USE_SHADER_AS_SUBASSET
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Unity.Profiling;
using UnityEditor.ShaderGraph.Internal;
using UnityEditor.VFX.Block;
using UnityEditor.VFX.UI;
using UnityEngine;
using UnityEngine.VFX;
using UnityEngine.Profiling;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.VFX
{
[InitializeOnLoad]
class VFXGraphPreprocessor : AssetPostprocessor
{
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
List<string> assetToReimport = null;
#if VFX_HAS_TIMELINE
UnityEditor.VFX.Migration.ActivationToControlTrack.SanitizePlayable(importedAssets);
#endif
if (deletedAssets.Any())
{
VFXViewWindow.GetAllWindows().ToList().ForEach(x => x.UpdateHistory());
}
var isAnySubgraphImported = importedAssets.Any(VisualEffectAssetModificationProcessor.IsVFXSubgraphExtension);
foreach (var assetPath in importedAssets)
{
bool isVFX = VisualEffectAssetModificationProcessor.HasVFXExtension(assetPath);
if (isVFX)
{
VisualEffectResource resource = VisualEffectResource.GetResourceAtPath(assetPath);
if (resource == null)
continue;
VFXGraph graph = resource.GetOrCreateGraph(); //resource.graph should be already != null at this stage but GetOrCreateGraph is also assigning the visualEffectResource. It's required for UpdateSubAssets
if (graph != null)
{
bool wasGraphSanitized = graph.sanitized;
try
{
graph.SanitizeForImport();
if (!wasGraphSanitized && graph.sanitized)
{
assetToReimport ??= new List<string>();
assetToReimport.Add(assetPath);
}
}
catch (Exception exception)
{
Debug.LogErrorFormat("Exception during sanitization of {0} : {1}", assetPath, exception);
}
var window = VFXViewWindow.GetWindow(graph, false, false);
if (window != null)
{
window.UpdateTitle(assetPath);
// Force blackboard update only when a subgraph gets re-imported
if (isAnySubgraphImported)
{
window.graphView?.blackboard.Update(true);
}
}
}
else
{
Debug.LogErrorFormat("VisualEffectGraphResource without graph : {0}", assetPath);
}
}
}
//Relaunch previously skipped OnCompileResource
if (assetToReimport != null)
{
AssetDatabase.StartAssetEditing();
foreach (var assetPath in assetToReimport)
{
try
{
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
}
catch (Exception exception)
{
Debug.LogErrorFormat("Exception during reimport of {0} : {1}", assetPath, exception);
}
}
AssetDatabase.StopAssetEditing();
}
}
static string[] OnAddResourceDependencies(string assetPath)
{
VisualEffectResource resource = VisualEffectResource.GetResourceAtPath(assetPath);
if (resource != null)
{
if (resource.graph is VFXGraph)
return resource.GetOrCreateGraph().UpdateImportDependencies();
Debug.LogError("VisualEffectGraphResource without graph");
}
return null;
}
static void OnCompileResource(VisualEffectResource resource)
{
if (resource != null)
{
VFXGraph graph = resource.graph as VFXGraph;
if (graph != null)
{
if (!graph.sanitized)
{
//Early return, the reimport will be forced with the next OnPostprocessAllAssets after Sanitize
resource.ClearRuntimeData();
}
else
{
//Workaround, use backup system to prevent any modification of the graph during compilation
//The responsible of this unexpected change is PrepareSubgraphs => RecurseSubgraphRecreateCopy => ResyncSlots
//It will let the VFXGraph in a really bad state after compilation.
graph = resource.GetOrCreateGraph();
var dependencies = new HashSet<ScriptableObject>();
dependencies.Add(graph);
graph.CollectDependencies(dependencies);
var backup = VFXMemorySerializer.StoreObjectsToByteArray(dependencies.ToArray(), CompressionLevel.None);
graph.errorManager.RefreshCompilationReport();
graph.CompileForImport();
VFXGraph.restoringGraph = true;
try
{
VFXMemorySerializer.ExtractObjects(backup, false);
}
finally
{
VFXGraph.restoringGraph = false;
}
//The backup during undo/redo is actually calling UnknownChange after ExtractObjects
//You have to avoid because it will call ResyncSlot
}
}
else
Debug.LogError("OnCompileResource error - VisualEffectResource without graph");
}
}
static void OnSetupMaterial(VisualEffectResource resource, Material material, UnityObject model)
{
if (resource != null)
{
// sanity checks
if (resource.graph == null)
{
Debug.LogError("OnSetupMaterial error - VisualEffectResource without graph");
return;
}
if (!(model is VFXModel))
{
Debug.LogError("OnSetupMaterial error - Passed object is not a VFXModel");
return;
}
//if (resource.graph != ((VFXModel)model).GetGraph())
//{
// Debug.LogError("OnSetupMaterial error - VisualEffectResource and VFXModel graph do not match");
// return;
//}
if (!resource.GetOrCreateGraph().sanitized)
{
Debug.LogError("OnSetupMaterial error - Graph hasn't been sanitized");
return;
}
// Actual call
if (model is IVFXSubRenderer)
{
((IVFXSubRenderer)model).SetupMaterial(material);
}
}
}
static VFXGraphPreprocessor()
{
EditorApplication.update += CheckCompilationVersion;
VisualEffectResource.onAddResourceDependencies = OnAddResourceDependencies;
VisualEffectResource.onCompileResource = OnCompileResource;
VisualEffectResource.onSetupMaterial = OnSetupMaterial;
}
static void CheckCompilationVersion()
{
EditorApplication.update -= CheckCompilationVersion;
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)
{
string[] allVisualEffectAssets = AssetDatabase.FindAssets("t:VisualEffectAsset");
compiledVersionProperty.intValue = (int)VFXGraphCompiledData.compiledVersion;
runtimeVersionProperty.intValue = (int)VisualEffectAsset.currentRuntimeDataVersion;
serializedVFXManager.ApplyModifiedProperties();
AssetDatabase.StartAssetEditing();
try
{
foreach (var guid in AssetDatabase.FindAssets("t:VisualEffectAsset"))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
AssetDatabase.ImportAsset(path);
}
VFXAssetManager.ImportAllVFXShaders();
}
finally
{
AssetDatabase.StopAssetEditing();
}
}
}
}
class VFXAssetManager : EditorWindow
{
public static Dictionary<VisualEffectObject, string> GetAllVisualEffectObjects()
{
var allVisualEffectObjects = new Dictionary<VisualEffectObject, string>();
var vfxObjectsGuid = AssetDatabase.FindAssets("t:VisualEffectObject");
foreach (var guid in vfxObjectsGuid)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var vfxObj = AssetDatabase.LoadAssetAtPath<VisualEffectObject>(assetPath);
if (vfxObj != null)
{
allVisualEffectObjects[vfxObj] = assetPath;
}
}
return allVisualEffectObjects;
}
public static Dictionary<Shader, string> GetAllShaderGraph()
{
var allShaderGraphObjects = new Dictionary<Shader, string>();
var shaderGraphGuids = AssetDatabase.FindAssets("t:Shader");
foreach (var guid in shaderGraphGuids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var shaderGraph = AssetDatabase.LoadAssetAtPath<Shader>(assetPath);
if (shaderGraph != null)
{
allShaderGraphObjects[shaderGraph] = assetPath;
}
}
return allShaderGraphObjects;
}
// Import VFX shader graph assets
// Because some shader compatible with VFX can be there before the Visual Effect package is installed
// We must re-import them to generate the ShaderGraphVfxAsset
public static void ImportAllVFXShaders()
{
var currentSrpBinder = VFXLibrary.currentSRPBinder;
if (currentSrpBinder != null)
{
foreach (var (shader, path) in GetAllShaderGraph())
{
var assets = AssetDatabase.LoadAllAssetsAtPath(path);
if (assets.OfType<ShaderGraphVfxAsset>().Any())
{
continue;
}
if (shader != null && currentSrpBinder.IsShaderVFXCompatible(shader))
{
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
}
}
}
}
public static void Build(bool forceDirty = false)
{
AssetDatabase.StartAssetEditing();
try
{
foreach (var vfxObj in GetAllVisualEffectObjects())
{
if (VFXViewPreference.advancedLogs)
Debug.Log($"Recompile VFX asset: {vfxObj.Key} ({vfxObj.Value})");
var resource = VisualEffectResource.GetResourceAtPath(vfxObj.Value);
if (resource != null)
{
AssetDatabase.ImportAsset(vfxObj.Value);
if (forceDirty)
EditorUtility.SetDirty(resource);
}
}
VFXExpression.ClearCache();
ImportAllVFXShaders();
}
finally
{
AssetDatabase.StopAssetEditing();
}
EditorUtility.UnloadUnusedAssetsImmediate();
GC.Collect();
}
[MenuItem("Edit/VFX/Rebuild And Save All VFX Graphs", priority = 10319)]
public static void BuildAndSave()
{
Build(true);
AssetDatabase.SaveAssets();
}
}
class VisualEffectAssetModificationProcessor : UnityEditor.AssetModificationProcessor
{
public static bool HasVFXExtension(string filePath)
{
if (!string.IsNullOrEmpty(filePath) &&
(filePath.EndsWith(VisualEffectResource.Extension, StringComparison.OrdinalIgnoreCase)
|| filePath.EndsWith(VisualEffectSubgraphBlock.Extension, StringComparison.OrdinalIgnoreCase)
|| filePath.EndsWith(VisualEffectSubgraphOperator.Extension, StringComparison.OrdinalIgnoreCase)))
{
// See this PR https://github.com/Unity-Technologies/Graphics/pull/6890
#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
return !AssetDatabase.IsValidFolder(filePath);
#else
return true;
#endif
}
return false;
}
public static bool IsVFXSubgraphExtension(string filePath)
{
return filePath.EndsWith(VisualEffectSubgraphBlock.Extension, StringComparison.OrdinalIgnoreCase)
|| filePath.EndsWith(VisualEffectSubgraphOperator.Extension, StringComparison.OrdinalIgnoreCase);
}
static string[] OnWillSaveAssets(string[] paths)
{
Profiler.BeginSample("VisualEffectAssetModicationProcessor.OnWillSaveAssets");
bool started = false;
try {
foreach (string path in paths.Where(HasVFXExtension))
{
if (!started)
{
started = true;
AssetDatabase.StartAssetEditing();
}
var vfxResource = VisualEffectResource.GetResourceAtPath(path);
if (vfxResource != null)
{
vfxResource.GetOrCreateGraph().UpdateSubAssets();
try
{
VFXGraph.compilingInEditMode = vfxResource.GetOrCreateGraph().GetCompilationMode() == VFXCompilationMode.Edition;
vfxResource.WriteAsset(); // write asset as the AssetDatabase won't do it.
}
finally
{
VFXGraph.compilingInEditMode = false;
}
}
}
}
finally
{
if (started)
AssetDatabase.StopAssetEditing();
}
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<VFXGraph>().FirstOrDefault();
}
if (graph == null)
{
graph = ScriptableObject.CreateInstance<VFXGraph>();
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((UnityEngine.Object)resource.asset ?? resource.subgraph, 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
// 7: Add CameraBuffer type
// 8: Bounds computation introduces a BoundsSettingMode for VFXDataParticles
// 9: Update HDRP decal angle fade encoding
// 10: Position Mesh and Skinned Mesh out of experimental (changing the list of flag and output types)
// 11: Instancing
// 12: Change space value of VFXSpace.None from 'int.MaxValue' to '-1'
// 13: Unexpected incorrect synchronization of output with ShaderGraph
// 14: ShaderGraph integration uses the material variant workflow
// 15: New ShaderGraph integration uses independent output
// 16: Add a collection of custom attributes (to be listed in blackboard)
// 17: New Flipbook player and split the different Flipbook modes in UVMode into separate variables
// 18: Change ProbabilitySampling m_IntegratedRandomDeprecated changed to m_Mode
public static readonly int CurrentVersion = 18;
[NonSerialized]
internal static bool compilingInEditMode = false;
public override void OnEnable()
{
base.OnEnable();
m_ExpressionGraphDirty = true;
}
public override void OnSRPChanged()
{
m_GraphSanitized = false;
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<VFXUI>();
}
return m_UIInfos;
}
}
[SerializeField]
List<VFXCustomAttributeDescriptor> m_CustomAttributes;
// Do not serialize custom attributes imported from sub-graphs
readonly List<VFXCustomAttributeDescriptor> m_DependenciesCustomAttributes = new();
public IEnumerable<VFXCustomAttributeDescriptor> customAttributes => (m_CustomAttributes ??= new List<VFXCustomAttributeDescriptor>()).Concat(m_DependenciesCustomAttributes);
public VFXParameterInfo[] m_ParameterInfo;
private VFXErrorManager m_ErrorManager;
private readonly VFXSystemNames m_SystemNames = new();
private readonly VFXAttributesManager m_AttributesManager = new();
public VFXErrorManager errorManager => m_ErrorManager ??= new VFXErrorManager();
public VFXSystemNames systemNames => m_SystemNames;
public VFXAttributesManager attributesManager => m_AttributesManager;
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 void SyncCustomAttributes()
{
m_CustomAttributes.RemoveAll(x => x == null);
foreach (var attributeDescriptor in customAttributes.ToArray())
{
attributeDescriptor.graph = this;
m_AttributesManager.TryRegisterCustomAttribute(attributeDescriptor.attributeName, attributeDescriptor.type, attributeDescriptor.description, out _);
var usages = GetCustomAttributeUsage(attributeDescriptor.attributeName).ToArray();
attributeDescriptor.ClearSubgraphUse();
foreach (var usage in usages.Where(VFXSubgraphUtility.IsSubgraphModel))
{
attributeDescriptor.AddSubgraphUse(usage.name);
}
// Remove custom attributes from sub-graphs that are not used by sub-graph anymore
if (attributeDescriptor.usedInSubgraphs == null && m_DependenciesCustomAttributes.Contains(attributeDescriptor))
{
m_DependenciesCustomAttributes.Remove(attributeDescriptor);
SetCustomAttributeDirty();
}
// Check if custom attribute is used, but not in sub-graph and not yet in the serialized collection
if (attributeDescriptor.usedInSubgraphs == null && usages.Length > 0 && !m_CustomAttributes.Contains(attributeDescriptor))
{
m_CustomAttributes.Add(attributeDescriptor);
attributeDescriptor.isReadOnly = false;
SetCustomAttributeDirty();
}
// Move custom attributes used in subgraph into the transient collection
else if (attributeDescriptor.usedInSubgraphs != null && m_CustomAttributes.Contains(attributeDescriptor))
{
m_CustomAttributes.Remove(attributeDescriptor);
if (!m_DependenciesCustomAttributes.Contains(attributeDescriptor))
{
m_DependenciesCustomAttributes.Add(attributeDescriptor);
}
attributeDescriptor.isReadOnly = true;
SetCustomAttributeDirty();
}
}
// Remove custom attributes from attribute manager if they do not exist anymore
foreach (var customAttribute in m_AttributesManager.GetCustomAttributes().ToArray())
{
if (customAttributes.All(x => string.Compare(x.attributeName, customAttribute.name, StringComparison.OrdinalIgnoreCase) != 0))
{
m_AttributesManager.UnregisterCustomAttribute(customAttribute.name);
}
}
}
public bool TryAddCustomAttribute(string attributeName, VFXValueType type, string description, bool isReadOnly, out VFXAttribute newAttribute)
{
var signature = CustomAttributeUtility.GetSignature(type);
if (m_AttributesManager.TryRegisterCustomAttribute(attributeName, signature, description, out newAttribute))
{
var customAttribute = ScriptableObject.CreateInstance<VFXCustomAttributeDescriptor>();
customAttribute.attributeName = newAttribute.name;
customAttribute.type = CustomAttributeUtility.GetSignature(type);
customAttribute.description = description;
customAttribute.graph = this;
customAttribute.isReadOnly = isReadOnly;
if (!isReadOnly)
{
m_CustomAttributes.Add(customAttribute);
}
else
{
m_DependenciesCustomAttributes.Add(customAttribute);
}
if (!isReadOnly) // if not from subgraph
Invalidate(InvalidationCause.kStructureChanged);
return true;
}
return false;
}
public bool IsCustomAttributeUsed(string attributeName)
{
// First look at operators
if (children
.OfType<IVFXAttributeUsage>()
.SelectMany(x => x.usedAttributes)
.Any(x => string.Compare(x.name, attributeName, StringComparison.OrdinalIgnoreCase) == 0))
return true;
// Look in context blocks
if (children
.OfType<VFXContext>()
.SelectMany(x => x.children)
.OfType<IVFXAttributeUsage>()
.SelectMany(x => x.usedAttributes)
.Distinct()
.Any(x => string.Compare(x.name, attributeName, StringComparison.OrdinalIgnoreCase) == 0))
return true;
return false;
}
public bool TryFindCustomAttributeDescriptor(string name, out VFXCustomAttributeDescriptor attributeDescriptor)
{
attributeDescriptor = customAttributes.SingleOrDefault(x => string.Compare(name, x.attributeName, StringComparison.OrdinalIgnoreCase) == 0);
return attributeDescriptor != null;
}
public IEnumerable<string> GetUnusedCustomAttributes()
{
var objs = new HashSet<ScriptableObject>();
CollectDependencies(objs, true);
var nodesUsingCustomAttribute = objs
.OfType<IVFXAttributeUsage>()
.SelectMany(x => x.usedAttributes)
.Where(x => this.attributesManager.IsCustom(x.name))
.Select(x => x.name)
.ToArray();
return this.attributesManager.GetCustomAttributeNames().Except(nodesUsingCustomAttribute);
}
public VFXAttribute DuplicateCustomAttribute(string attributeName)
{
var newAttribute = m_AttributesManager.Duplicate(attributeName);
this.TryAddCustomAttribute(newAttribute.name, newAttribute.type, newAttribute.description, false, out var attribute);
return attribute;
}
public void RemoveCustomAttribute(string attributeName)
{
var existingAttribute = this.FindCustomAttribute(attributeName);
if (existingAttribute != null)
{
foreach (var usage in GetCustomAttributeUsage(attributeName).ToArray())
{
if (Selection.Contains(usage))
Selection.Remove(usage);
RemoveModel(usage);
}
m_AttributesManager.UnregisterCustomAttribute(attributeName);
m_CustomAttributes.Remove(existingAttribute);
Invalidate(this, InvalidationCause.kStructureChanged);
}
}
public bool TryRenameCustomAttribute(string oldName, string newName)
{
var customAttributeDescriptor = FindCustomAttribute(oldName);
var usingNodes = GetRecursiveChildren()
.OfType<IVFXAttributeUsage>()
.Where(x => x.usedAttributes.Any(x => string.Compare(x.name, oldName, StringComparison.OrdinalIgnoreCase) == 0))
.ToArray();
var result = this.m_AttributesManager.TryRename(oldName, newName);
if (result == RenameStatus.Success)
{
customAttributeDescriptor.attributeName = newName;
foreach (var customAttributeNode in usingNodes)
{
customAttributeNode.Rename(oldName, newName);
}
Invalidate(this, InvalidationCause.kStructureChanged);
return true;
}
// Already renamed
if (result == RenameStatus.NotFound && FindCustomAttribute(newName) != null)
{
return true;
}
if (result == RenameStatus.NameUsed)
{
Debug.LogWarning("You are trying to rename a custom attribute with a name that is already used by another custom attribute");
}
return false;
}
public bool TryUpdateCustomAttribute(string attributeName, CustomAttributeUtility.Signature type, string description, bool? isReadOnly = null)
{
var customAttributeDescriptor = this.FindCustomAttribute(attributeName);
if (this.attributesManager.TryUpdate(attributeName, type, description))
{
customAttributeDescriptor.type = type;
customAttributeDescriptor.description = description;
var usingNodes = GetRecursiveChildren()
.OfType<IVFXAttributeUsage>()
.Where(x => x.usedAttributes.Any(x => string.Compare(x.name, attributeName, StringComparison.OrdinalIgnoreCase) == 0));
foreach (var node in usingNodes)
{
((VFXModel)node).Invalidate(InvalidationCause.kSettingChanged);
}
if (isReadOnly == false || (isReadOnly == null && !customAttributeDescriptor.isReadOnly)) // if not from subgraph
Invalidate(this, InvalidationCause.kStructureChanged);
return true;
}
if (customAttributeDescriptor != null && isReadOnly.HasValue && isReadOnly.Value != customAttributeDescriptor.isReadOnly)
{
customAttributeDescriptor.isReadOnly = isReadOnly.Value;
if (isReadOnly.Value)
{
m_CustomAttributes.Remove(customAttributeDescriptor);
if (!m_DependenciesCustomAttributes.Contains(customAttributeDescriptor))
{
m_DependenciesCustomAttributes.Add(customAttributeDescriptor);
}
}
else
{
if (!m_CustomAttributes.Contains(customAttributeDescriptor))
{
m_CustomAttributes.Add(customAttributeDescriptor);
}
m_DependenciesCustomAttributes.Remove(customAttributeDescriptor);
}
}
return false;
}
public void SetCustomAttributeExpanded(string attributeName, bool isExpanded)
{
var customAttributeDescriptor = this.FindCustomAttribute(attributeName);
customAttributeDescriptor.isExpanded = isExpanded;
}
public object Backup()
{
Profiler.BeginSample("VFXGraph.Backup");
var dependencies = new HashSet<ScriptableObject>();
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);
var graph = scriptableObject.OfType<VFXGraph>().Single();
graph.SyncCustomAttributes();
Profiler.BeginSample("VFXGraph.Restore SendUnknownChange");
foreach (var model in scriptableObject.OfType<VFXModel>())
{
model.OnUnknownChange();
}
Profiler.EndSample();
Profiler.EndSample();
m_SystemNames.Sync(this);
m_ExpressionGraphDirty = true;
m_ExpressionValuesDirty = true;
m_DependentDirty = true;
SetCustomAttributeDirty();
}
public override void CollectDependencies(HashSet<ScriptableObject> objs, bool ownedOnly = true)
{
Profiler.BeginSample("VFXEditor.CollectDependencies");
try
{
if (m_UIInfos != null)
objs.Add(m_UIInfos);
m_CustomAttributes?.ForEach(x => { if (x != null) objs.Add(x); });
base.CollectDependencies(objs, ownedOnly);
}
finally
{
Profiler.EndSample();
}
}
static readonly ProfilerMarker k_ProfilerMarkerSanitizeGraph = new("VFXEditor.SanitizeGraph");
public void SanitizeGraph()
{
if (m_GraphSanitized)
return;
using var profilerScope = k_ProfilerMarkerSanitizeGraph.Auto();
var objs = new HashSet<ScriptableObject>();
CollectDependencies(objs);
if (version < 7)
{
SanitizeCameraBuffers(objs);
}
SyncCustomAttributes();
foreach (var model in objs.OfType<VFXModel>())
{
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);
if (version < 11)
{
visualEffectResource.instancingMode = VFXInstancingMode.Disabled;
}
if (version < 14)
{
objs
.OfType<IVFXAttributeUsage>()
.SelectMany(x => x.usedAttributes)
.Where(x => m_AttributesManager.IsCustom(x.name))
.GroupBy(x => x.name)
.Select(x => x.First())
.Where(x => customAttributes.All(y => y.attributeName != x.name))
.ToList()
.ForEach(x => TryAddCustomAttribute(x.name, x.type, string.Empty, false, out _));
}
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<VFXSlotGradient>())
{
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;
UpdateSubAssets(); //Force remove no more referenced object from the asset & *important* register as persistent new dependencies
}
private IEnumerable<VFXModel> GetCustomAttributeUsage(string attributeName)
{
bool IsAttributeUsed(IVFXAttributeUsage attributeUsage, string attrName)
{
return attributeUsage.usedAttributes.Any(x => string.Compare(x.name, attrName, StringComparison.OrdinalIgnoreCase) == 0);
}
foreach (var child in children.Where(x => x is IVFXAttributeUsage))
{
if (IsAttributeUsed((IVFXAttributeUsage)child, attributeName))
yield return child;
}
foreach (var context in children.OfType<VFXContext>())
{
foreach (var block in context.children)
{
if (IsAttributeUsed(block, attributeName))
yield return block;
}
}
}
private VFXCustomAttributeDescriptor FindCustomAttribute(string attributeName)
{
return customAttributes.FirstOrDefault(x => string.Compare(attributeName, x.attributeName, StringComparison.OrdinalIgnoreCase) == 0);
}
private void SanitizeCameraBuffers(HashSet<ScriptableObject> objs)
{
List<Tuple<int, string, int, string>> links = new List<Tuple<int, string, int, string>>();
var cameraSlots = objs.Where(obj => obj is VFXSlot && (obj as VFXSlot).value is CameraType).ToArray();
for (int i = 0; i < cameraSlots.Length; ++i)
{
var cameraSlot = cameraSlots[i] as VFXSlot;
var depthBufferSlot = cameraSlot.children.First(slot => slot.name == "depthBuffer");
SanitizeCameraBufferLinks(depthBufferSlot, i, cameraSlots, links);
var colorBufferSlot = cameraSlot.children.First(slot => slot.name == "colorBuffer");
SanitizeCameraBufferLinks(colorBufferSlot, i, cameraSlots, links);
objs.Remove(cameraSlots[i]);
cameraSlots[i] = cameraSlot.Recreate();
objs.Add(cameraSlots[i]);
}
foreach (var link in links)
{
var cameraSlotFrom = cameraSlots[link.Item1] as VFXSlot;
var slotFrom = cameraSlotFrom.children.First(slot => slot.name == link.Item2);
var cameraSlotTo = cameraSlots[link.Item3] as VFXSlot;
var slotTo = cameraSlotTo.children.First(slot => slot.name == link.Item4);
slotFrom.Link(slotTo);
}
}
private void SanitizeCameraBufferLinks(VFXSlot slotFrom, int indexFrom, ScriptableObject[] cameraSlots, List<Tuple<int, string, int, string>> links)
{
if (slotFrom != null && !(slotFrom is VFXSlotCameraBuffer))
{
foreach (var slotTo in slotFrom.LinkedSlots)
{
int indexTo = Array.IndexOf(cameraSlots, slotTo.GetMasterSlot());
if (indexTo >= 0)
{
links.Add(new Tuple<int, string, int, string>(indexFrom, slotFrom.name, indexTo, slotTo.name));
}
}
}
}
public void ClearCompileData()
{
m_CompiledData = null;
m_ExpressionValuesDirty = true;
}
[SerializeField]
List<string> m_ImportDependencies;
public void UpdateSubAssets()
{
if (visualEffectResource == null)
return;
Profiler.BeginSample("VFXEditor.UpdateSubAssets");
try
{
var currentObjects = new HashSet<ScriptableObject>();
currentObjects.Add(this);
CollectDependencies(currentObjects);
visualEffectResource.SetContents(currentObjects.Cast<UnityObject>().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 == InvalidationCause.kStructureChanged ||
cause == InvalidationCause.kParamChanged ||
cause == InvalidationCause.kSettingChanged ||
cause == InvalidationCause.kSpaceChanged ||
cause == InvalidationCause.kConnectionChanged ||
cause == InvalidationCause.kUIChanged) &&
(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;
}
if (cause == VFXModel.InvalidationCause.kMaterialChanged)
{
m_MaterialsDirty = true;
}
}
public uint FindReducedExpressionIndexFromSlotCPU(VFXSlot slot)
{
RecompileIfNeeded(false, true);
return compiledData.FindReducedExpressionIndexFromSlotCPU(slot);
}
public void SetCompilationMode(VFXCompilationMode mode, bool reimport = true)
{
if (m_CompilationMode != mode)
{
m_CompilationMode = mode;
SetExpressionGraphDirty();
if (reimport)
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(this));
}
}
public VFXCompilationMode GetCompilationMode()
{
return m_CompilationMode;
}
public void ForceShaderDebugSymbols(bool enable, bool reimport = true)
{
if (m_ForceShaderDebugSymbols != enable)
{
m_ForceShaderDebugSymbols = enable;
if (reimport)
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(this));
}
}
public bool GetForceShaderDebugSymbols()
{
return m_ForceShaderDebugSymbols;
}
public void SetForceShaderValidation(bool forceShaderValidation, bool reimport = true)
{
if (m_ForceShaderValidation != forceShaderValidation)
{
m_ForceShaderValidation = forceShaderValidation;
if (m_ForceShaderValidation)
{
SetExpressionGraphDirty();
if (reimport)
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 bool IsCustomAttributeDirty() => m_CustomAttributesDirty;
public void SetCustomAttributeDirty(bool isDirty = true) => m_CustomAttributesDirty = isDirty;
public void BuildSubgraphDependencies()
{
if (m_SubgraphDependencies == null)
m_SubgraphDependencies = new List<VisualEffectObject>();
else
m_SubgraphDependencies.Clear();
HashSet<VisualEffectObject> explored = new HashSet<VisualEffectObject>();
RecurseBuildDependencies(explored, children);
}
void RecurseBuildDependencies(HashSet<VisualEffectObject> explored, IEnumerable<VFXModel> 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<VFXModel> 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.UpdateOutputExpressionsIfNeeded();
}
}
}
private void SetFlattenedParentToSubblocks()
{
foreach (var child in children.OfType<VFXContext>())
foreach (var block in child.children.OfType<VFXSubgraphBlock>())
block.SetSubblocksFlattenedParent();
}
void RecurseSubgraphPatchInputExpression(IEnumerable<VFXModel> 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.UpdateOutputExpressionsIfNeeded();
}
}
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<VFXGraph> GetAllGraphs<T>() 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<T>(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;
//Set to true when restoring graph post compilation. Some costly behavior can be skipped in that situation (like reloading the whole UI). This is a safe hack.
public static bool restoringGraph { get; set; } = false;
public void SanitizeForImport()
{
// We arrive from AssetPostProcess so dependencies are already loaded no need to worry about them (FB #1364156)
SyncCustomAttributes();
foreach (var child in children)
child.CheckGraphBeforeImport();
SanitizeGraph();
}
public void CompileForImport()
{
if (compilingInEditMode)
m_CompilationMode = VFXCompilationMode.Edition;
SyncCustomAttributes();
if (!GetResource().isSubgraph)
{
// Check Graph Before Import can be needed to synchronize modified shaderGraph
foreach (var child in children)
child.CheckGraphBeforeImport();
// Graph must have been sanitized at this point by the VFXGraphPreprocessor.OnPreprocess
BuildSubgraphDependencies();
PrepareSubgraphs();
compiledData.Compile(m_CompilationMode, m_ForceShaderValidation, VFXViewPreference.generateShadersWithDebugSymbols || m_ForceShaderDebugSymbols, VFXAnalytics.GetInstance());
}
m_ExpressionGraphDirty = false;
m_ExpressionValuesDirty = false;
}
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, VFXViewPreference.generateShadersWithDebugSymbols || m_ForceShaderDebugSymbols, VFXAnalytics.GetInstance());
}
else
{
if (m_ExpressionValuesDirty && !m_ExpressionGraphDirty)
compiledData.UpdateValues();
if (m_MaterialsDirty && GetResource().asset != null)
UnityEngine.VFX.VFXManager.ResyncMaterials(GetResource().asset);
}
if (considerGraphDirty)
m_ExpressionGraphDirty = false;
m_ExpressionValuesDirty = false;
m_MaterialsDirty = false;
}
else if (m_ExpressionGraphDirty && !preventRecompilation)
{
BuildSubgraphDependencies();
PrepareSubgraphs();
m_ExpressionGraphDirty = false;
}
if (!preventDependencyRecompilation && m_DependentDirty)
{
var obj = GetResource().visualEffectObject;
foreach (var graph in GetAllGraphs<VisualEffectAsset>())
{
graph.SubgraphDirty(obj);
}
m_DependentDirty = false;
}
errorManager.GenerateErrors();
}
public void RegisterCompileError(string error, string description, VFXModel model)
{
errorManager.compileReporter.RegisterError(error, VFXErrorType.Error, description, model);
}
private VFXGraphCompiledData compiledData
{
get
{
if (m_CompiledData == null)
m_CompiledData = new VFXGraphCompiledData(this);
return m_CompiledData;
}
}
public bool sanitized { get { return m_GraphSanitized; } }
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 bool m_MaterialsDirty = false;
[NonSerialized]
private bool m_CustomAttributesDirty = false;
[NonSerialized]
private VFXGraphCompiledData m_CompiledData;
private VFXCompilationMode m_CompilationMode = VFXCompilationMode.Runtime;
private bool m_ForceShaderDebugSymbols = false;
private bool m_ForceShaderValidation = false;
[NonSerialized]
public Action<VFXGraph> onRuntimeDataChanged;
[SerializeField]
private List<VisualEffectObject> m_SubgraphDependencies = new List<VisualEffectObject>();
[SerializeField]
private string m_CategoryPath;
public string categoryPath
{
get { return m_CategoryPath; }
set { m_CategoryPath = value; }//TODO invalidate cache here
}
public ReadOnlyCollection<VisualEffectObject> subgraphDependencies
{
get { return m_SubgraphDependencies.AsReadOnly(); }
}
public string[] UpdateImportDependencies()
{
visualEffectResource.ClearImportDependencies();
var dependencies = new HashSet<int>();
GetImportDependentAssets(dependencies);
var guids = new HashSet<string>();
foreach (var dependency in dependencies)
{
if (dependency == 0)
continue;
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(dependency, out string guid, out long localId))
{
if (!guids.Contains(guid))
{
guids.Add(guid);
visualEffectResource.AddImportDependency(guid);
}
}
}
return guids.ToArray();
}
private VisualEffectResource m_Owner;
}
}