Rasagar/Library/PackageCache/com.unity.visualeffectgraph/Editor/Inspector/VFXAssetEditor.cs
2024-08-26 23:07:20 +03:00

977 lines
41 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEditorInternal;
using UnityEditor;
using UnityEngine;
using UnityEngine.VFX;
using UnityEditor.Callbacks;
using UnityEditor.VFX;
using UnityEditor.VFX.UI;
using UnityEngine.UIElements;
using UnityObject = UnityEngine.Object;
class VFXExternalShaderProcessor : AssetPostprocessor
{
public const string k_ShaderDirectory = "Shaders";
public const string k_ShaderExt = ".vfxshader";
public static bool allowExternalization { get { return EditorPrefs.GetBool(VFXViewPreference.allowShaderExternalizationKey, false); } }
void OnPreprocessAsset()
{
if (!allowExternalization)
return;
bool isVFX = assetPath.EndsWith(VisualEffectResource.Extension);
if (isVFX)
{
string vfxName = Path.GetFileNameWithoutExtension(assetPath);
string vfxDirectory = Path.GetDirectoryName(assetPath);
string shaderDirectory = vfxDirectory + "/" + k_ShaderDirectory + "/" + vfxName;
if (!Directory.Exists(shaderDirectory))
{
return;
}
VisualEffectAsset asset = AssetDatabase.LoadAssetAtPath<VisualEffectAsset>(assetPath);
if (asset == null)
return;
bool oneFound = false;
VisualEffectResource resource = asset.GetResource();
if (resource == null)
return;
VFXShaderSourceDesc[] descs = resource.shaderSources;
foreach (var shaderPath in Directory.GetFiles(shaderDirectory))
{
if (shaderPath.EndsWith(k_ShaderExt))
{
System.IO.StreamReader file = new System.IO.StreamReader(shaderPath);
string shaderLine = file.ReadLine();
file.Close();
if (shaderLine == null || !shaderLine.StartsWith("//"))
continue;
string[] shaderParams = shaderLine.Split(',');
string shaderName = shaderParams[0].Substring(2);
int index;
if (!int.TryParse(shaderParams[1], out index))
continue;
if (index < 0 || index >= descs.Length)
continue;
if (descs[index].name != shaderName)
continue;
string shaderSource = File.ReadAllText(shaderPath);
//remove the first two lines that where added when externalized
shaderSource = shaderSource.Substring(shaderSource.IndexOf("\n", shaderSource.IndexOf("\n") + 1) + 1);
descs[index].source = shaderSource;
oneFound = true;
}
}
if (oneFound)
{
resource.shaderSources = descs;
}
}
}
}
[CustomEditor(typeof(VisualEffectAsset))]
[CanEditMultipleObjects]
class VisualEffectAssetEditor : UnityEditor.Editor
{
#if UNITY_2021_1_OR_NEWER
[OnOpenAsset(OnOpenAssetAttributeMode.Validate)]
public static bool WillOpenInUnity(int instanceID)
{
var obj = EditorUtility.InstanceIDToObject(instanceID);
if (obj is VFXGraph || obj is VFXModel || obj is VFXUI)
return true;
else if (obj is VisualEffectAsset)
return true;
else if (obj is VisualEffectSubgraph)
return true;
return false;
}
#endif
[OnOpenAsset(1)]
public static bool OnOpenVFX(int instanceID, int line)
{
var obj = EditorUtility.InstanceIDToObject(instanceID);
if (obj is VFXGraph || obj is VFXModel || obj is VFXUI)
{
// for visual effect graph editor ScriptableObject select them when double clicking on them.
//Since .vfx importer is a copyasset, the default is to open it with an external editor.
Selection.activeInstanceID = instanceID;
return true;
}
else if (obj is VisualEffectAsset vfxAsset)
{
var window = VFXViewWindow.GetWindow(vfxAsset, false);
if (window == null)
{
window = VFXViewWindow.GetWindow(vfxAsset, true);
}
window.LoadAsset(vfxAsset, null);
window.Focus();
return true;
}
else if (obj is VisualEffectSubgraph)
{
VisualEffectResource resource = VisualEffectResource.GetResourceAtPath(AssetDatabase.GetAssetPath(obj));
var window = VFXViewWindow.GetWindow(resource, false);
if (window == null)
{
window = VFXViewWindow.GetWindow(resource, true);
window.LoadResource(resource, null);
}
window.Focus();
return true;
}
else if (obj is Material || obj is ComputeShader)
{
string path = AssetDatabase.GetAssetPath(instanceID);
if (path.EndsWith(VisualEffectResource.Extension))
{
var resource = VisualEffectResource.GetResourceAtPath(path);
if (resource != null)
{
int index = resource.GetShaderIndex(obj);
resource.ShowGeneratedShaderFile(index, line);
return true;
}
}
}
return false;
}
ReorderableList m_ReorderableList;
List<IVFXSubRenderer> m_OutputContexts = new List<IVFXSubRenderer>();
VFXGraph m_CurrentGraph;
void OnReorder(ReorderableList list)
{
for (int i = 0; i < m_OutputContexts.Count(); ++i)
{
m_OutputContexts[i].vfxSystemSortPriority = i;
}
if (VFXViewWindow.GetAllWindows().All(x => x.graphView?.controller?.graph.visualEffectResource.GetInstanceID() != m_CurrentGraph.visualEffectResource.GetInstanceID() || !x.hasFocus))
{
// Do we need a compileReporter here?
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(m_CurrentGraph.visualEffectResource));
}
}
private void DrawOutputContextItem(Rect rect, int index, bool isActive, bool isFocused)
{
var context = m_OutputContexts[index] as VFXContext;
var systemName = context.GetGraph().systemNames.GetUniqueSystemName(context.GetData());
var contextLetter = context.letter;
var contextName = string.IsNullOrEmpty(context.label) ? context.name.Replace('\n', ' ') : context.label;
var fullName = string.Format("{0}{1}/{2}", systemName, contextLetter != '\0' ? "/" + contextLetter : string.Empty, contextName.Replace('\n', ' '));
EditorGUI.LabelField(rect, EditorGUIUtility.TempContent(fullName));
}
private void DrawHeader(Rect rect)
{
EditorGUI.LabelField(rect, EditorGUIUtility.TrTextContent("Output Render Order"));
}
static Mesh s_CubeWireFrame;
void OnEnable()
{
m_OutputContexts.Clear();
VisualEffectAsset vfxTarget = target as VisualEffectAsset;
var resource = vfxTarget.GetResource();
if (resource != null) //Can be null if VisualEffectAsset is in Asset Bundle
{
m_CurrentGraph = resource.GetOrCreateGraph();
m_CurrentGraph.systemNames.Sync(m_CurrentGraph);
m_OutputContexts.AddRange(m_CurrentGraph.children.OfType<IVFXSubRenderer>().OrderBy(t => t.vfxSystemSortPriority));
}
if (s_PlayPauseIcons == null)
{
s_PlayPauseIcons = new[]
{
EditorGUIUtility.TrIconContent("PlayButton", "Animate preview"),
EditorGUIUtility.TrIconContent("PauseButton", "Pause preview animation"),
};
}
m_ReorderableList = new ReorderableList(m_OutputContexts, typeof(IVFXSubRenderer));
m_ReorderableList.displayRemove = false;
m_ReorderableList.displayAdd = false;
m_ReorderableList.onReorderCallback = OnReorder;
m_ReorderableList.drawHeaderCallback = DrawHeader;
m_ReorderableList.drawElementCallback = DrawOutputContextItem;
var targetResources = targets.Cast<VisualEffectAsset>().Select(t => t.GetResource()).Where(t => t != null).ToArray();
if (targetResources.Any())
{
resourceObject = new SerializedObject(targetResources);
resourceUpdateModeProperty = resourceObject.FindProperty("m_Infos.m_UpdateMode");
cullingFlagsProperty = resourceObject.FindProperty("m_Infos.m_CullingFlags");
motionVectorRenderModeProperty = resourceObject.FindProperty("m_Infos.m_RendererSettings.motionVectorGenerationMode");
prewarmDeltaTime = resourceObject.FindProperty("m_Infos.m_PreWarmDeltaTime");
prewarmStepCount = resourceObject.FindProperty("m_Infos.m_PreWarmStepCount");
initialEventName = resourceObject.FindProperty("m_Infos.m_InitialEventName");
instancingModeProperty = resourceObject.FindProperty("m_Infos.m_InstancingMode");
instancingCapacityProperty = resourceObject.FindProperty("m_Infos.m_InstancingCapacity");
}
if (targets?.Length > 0)
{
targetObject = new SerializedObject(targets);
instancingDisabledReasonProperty = targetObject.FindProperty("m_Infos.m_InstancingDisabledReason");
}
}
private void CreateVisualEffect()
{
Debug.Assert(m_VisualEffectGO == null);
m_PreviewUtility?.Cleanup();
m_PreviewUtility = new PreviewRenderUtility();
m_PreviewUtility.camera.fieldOfView = 60.0f;
m_PreviewUtility.camera.allowHDR = true;
m_PreviewUtility.camera.allowMSAA = false;
m_PreviewUtility.camera.farClipPlane = 10000.0f;
m_PreviewUtility.camera.clearFlags = CameraClearFlags.SolidColor;
m_PreviewUtility.ambientColor = new Color(.1f, .1f, .1f, 1.0f);
m_PreviewUtility.lights[0].intensity = 1.4f;
m_PreviewUtility.lights[0].transform.rotation = Quaternion.Euler(40f, 40f, 0);
m_PreviewUtility.lights[1].intensity = 1.4f;
m_VisualEffectGO = new GameObject("VisualEffect (Preview)");
m_VisualEffectGO.hideFlags = HideFlags.DontSave;
m_VisualEffect = m_VisualEffectGO.AddComponent<VisualEffect>();
m_VisualEffect.pause = true;
m_RemainingFramesToRender = 1;
m_PreviewUtility.AddManagedGO(m_VisualEffectGO);
m_VisualEffectGO.transform.localPosition = Vector3.zero;
m_VisualEffectGO.transform.localRotation = Quaternion.identity;
m_VisualEffectGO.transform.localScale = Vector3.one;
VisualEffectAsset vfxTarget = target as VisualEffectAsset;
m_VisualEffect.visualEffectAsset = vfxTarget;
m_CurrentBounds = new Bounds(Vector3.zero, Vector3.one);
m_Distance = null;
m_Angles = Vector2.zero;
if (s_CubeWireFrame == null)
{
s_CubeWireFrame = new Mesh();
var vertices = new Vector3[]
{
new Vector3(-0.5f, -0.5f, -0.5f),
new Vector3(-0.5f, -0.5f, 0.5f),
new Vector3(-0.5f, 0.5f, 0.5f),
new Vector3(-0.5f, 0.5f, -0.5f),
new Vector3(0.5f, -0.5f, -0.5f),
new Vector3(0.5f, -0.5f, 0.5f),
new Vector3(0.5f, 0.5f, 0.5f),
new Vector3(0.5f, 0.5f, -0.5f)
};
var indices = new int[]
{
0, 1,
0, 3,
0, 4,
6, 2,
6, 5,
6, 7,
1, 2,
1, 5,
3, 7,
3, 2,
4, 5,
4, 7
};
s_CubeWireFrame.vertices = vertices;
s_CubeWireFrame.SetIndices(indices, MeshTopology.Lines, 0);
}
}
PreviewRenderUtility m_PreviewUtility;
GameObject m_VisualEffectGO;
VisualEffect m_VisualEffect;
Vector2 m_Angles;
float? m_Distance;
int m_RemainingFramesToRender;
Bounds m_CurrentBounds;
const int kSafeFrame = 2;
public override bool HasPreviewGUI()
{
return !serializedObject.isEditingMultipleObjects;
}
void ComputeFarNear(float distance)
{
if (m_CurrentBounds.size != Vector3.zero)
{
float maxBounds = Mathf.Sqrt(m_CurrentBounds.size.x * m_CurrentBounds.size.x + m_CurrentBounds.size.y * m_CurrentBounds.size.y + m_CurrentBounds.size.z * m_CurrentBounds.size.z);
m_PreviewUtility.camera.farClipPlane = distance + maxBounds * 1.1f;
m_PreviewUtility.camera.nearClipPlane = Mathf.Max(0.0001f, (distance - maxBounds));
m_PreviewUtility.camera.nearClipPlane = Mathf.Max(0.0001f, (distance - maxBounds));
}
}
public override void OnPreviewSettings()
{
EditorGUI.BeginChangeCheck();
int isAnimatedState = m_IsAnimated ? 1 : 0;
m_IsAnimated = PreviewGUI.CycleButton(isAnimatedState, s_PlayPauseIcons) == 1;
if (EditorGUI.EndChangeCheck())
{
m_VisualEffect.pause = !m_IsAnimated;
if (!m_IsAnimated)
{
StopRendering();
}
}
GUI.enabled = m_IsAnimated;
// Random id=10012 because when set to 0 the button get highlighted by default !?
if (EditorGUILayout.IconButton(10012, EditorGUIUtility.TrIconContent("Refresh", "Restart VFX"), EditorStyles.toolbarButton, null))
{
m_VisualEffect.Reinit();
}
GUI.enabled = true;
}
private static GUIContent[] s_PlayPauseIcons;
private bool m_IsAnimated;
private Rect m_LastArea;
public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
{
if (m_VisualEffectGO == null)
CreateVisualEffect();
bool isRepaint = Event.current.type == EventType.Repaint;
Renderer renderer = m_VisualEffectGO.GetComponent<Renderer>();
if (renderer == null)
return;
if (isRepaint && r != m_LastArea)
RequestSingleFrame();
if (VFXPreviewGUI.TryDrag2D(ref m_Angles, m_LastArea))
RequestSingleFrame();
if (renderer.bounds.size != Vector3.zero)
{
m_CurrentBounds = renderer.bounds;
//make sure that none of the bounds values are 0
if (m_CurrentBounds.size.x == 0)
{
Vector3 size = m_CurrentBounds.size;
size.x = (m_CurrentBounds.size.y + m_CurrentBounds.size.z) * 0.1f;
m_CurrentBounds.size = size;
}
if (m_CurrentBounds.size.y == 0)
{
Vector3 size = m_CurrentBounds.size;
size.y = (m_CurrentBounds.size.x + m_CurrentBounds.size.z) * 0.1f;
m_CurrentBounds.size = size;
}
if (m_CurrentBounds.size.z == 0)
{
Vector3 size = m_CurrentBounds.size;
size.z = (m_CurrentBounds.size.x + m_CurrentBounds.size.y) * 0.1f;
m_CurrentBounds.size = size;
}
}
if (!m_Distance.HasValue && m_RemainingFramesToRender == 1)
{
float maxBounds = Mathf.Sqrt(m_CurrentBounds.size.x * m_CurrentBounds.size.x +
m_CurrentBounds.size.y * m_CurrentBounds.size.y +
m_CurrentBounds.size.z * m_CurrentBounds.size.z);
m_Distance = Mathf.Max(0.01f, maxBounds * 1.25f);
ComputeFarNear(0f);
}
else
{
ComputeFarNear(m_Distance.GetValueOrDefault(0f));
}
if (Event.current.isScrollWheel)
{
m_Distance *= 1 + Event.current.delta.y * .015f;
RequestSingleFrame();
}
if (m_Mat == null)
m_Mat = (Material)EditorGUIUtility.LoadRequired("SceneView/HandleLines.mat");
if (!isRepaint)
{
if (m_RemainingFramesToRender > 0)
Repaint();
return;
}
if (r.width > 50 && r.height > 50)
m_LastArea = r;
bool needsRender = m_IsAnimated || m_RemainingFramesToRender > 0;
if (needsRender)
{
m_RemainingFramesToRender--;
m_PreviewUtility.BeginPreview(m_LastArea, background);
Quaternion rot = Quaternion.Euler(0, m_Angles.x, 0) * Quaternion.Euler(m_Angles.y, 0, 0);
m_PreviewUtility.camera.transform.position = m_CurrentBounds.center + rot * new Vector3(0, 0, -m_Distance.GetValueOrDefault(0));
m_PreviewUtility.camera.transform.localRotation = rot;
if (m_Distance.HasValue)
m_PreviewUtility.DrawMesh(s_CubeWireFrame, Matrix4x4.TRS(m_CurrentBounds.center, Quaternion.identity, m_CurrentBounds.size), m_Mat, 0);
m_PreviewUtility.Render(true);
m_PreviewUtility.EndAndDrawPreview(m_LastArea);
}
if (!m_IsAnimated && m_RemainingFramesToRender == 0)
StopRendering();
if (m_IsAnimated)
Repaint();
else
EditorGUI.DrawPreviewTexture(m_LastArea, m_PreviewUtility.renderTexture);
}
void RequestSingleFrame()
{
if (m_RemainingFramesToRender < 0)
m_RemainingFramesToRender = 1;
}
void StopRendering()
{
m_RemainingFramesToRender = -1;
}
Material m_Mat;
void OnDisable()
{
if (!UnityObject.ReferenceEquals(m_VisualEffectGO, null))
{
UnityObject.DestroyImmediate(m_VisualEffectGO);
}
if (m_PreviewUtility != null)
{
m_PreviewUtility.Cleanup();
}
}
private static readonly GUIContent[] k_CullingOptionsContents = new GUIContent[]
{
EditorGUIUtility.TrTextContent("Recompute bounds and simulate when visible"),
EditorGUIUtility.TrTextContent("Always recompute bounds, simulate only when visible"),
EditorGUIUtility.TrTextContent("Always recompute bounds and simulate")
};
static readonly VFXCullingFlags[] k_CullingOptionsValue = new VFXCullingFlags[]
{
VFXCullingFlags.CullSimulation | VFXCullingFlags.CullBoundsUpdate,
VFXCullingFlags.CullSimulation,
VFXCullingFlags.CullNone,
};
private static readonly GUIContent k_InstancingContent = EditorGUIUtility.TrTextContent("Instancing");
private static readonly GUIContent k_InstancingModeContent = EditorGUIUtility.TrTextContent("Instancing Mode", "Selects how the visual effect will be handled regarding instancing.");
private static readonly GUIContent k_InstancingCapacityContent = EditorGUIUtility.TrTextContent("Max Batch Capacity", "Max number of instances that can be grouped together in a single batch.");
SerializedObject resourceObject;
SerializedProperty resourceUpdateModeProperty;
SerializedProperty cullingFlagsProperty;
SerializedProperty motionVectorRenderModeProperty;
SerializedProperty prewarmDeltaTime;
SerializedProperty prewarmStepCount;
SerializedProperty initialEventName;
SerializedProperty instancingModeProperty;
SerializedProperty instancingCapacityProperty;
SerializedObject targetObject;
SerializedProperty instancingDisabledReasonProperty;
private static readonly float k_MinimalCommonDeltaTime = 1.0f / 800.0f;
public static void DisplayPrewarmInspectorGUI(SerializedObject resourceObject, SerializedProperty prewarmDeltaTime, SerializedProperty prewarmStepCount)
{
if (!prewarmDeltaTime.hasMultipleDifferentValues && !prewarmStepCount.hasMultipleDifferentValues)
{
var currentDeltaTime = prewarmDeltaTime.floatValue;
var currentStepCount = prewarmStepCount.uintValue;
var currentTotalTime = currentDeltaTime * currentStepCount;
EditorGUI.BeginChangeCheck();
currentTotalTime = EditorGUILayout.FloatField(EditorGUIUtility.TrTextContent("PreWarm Total Time", "Sets the time in seconds to advance the current effect to when it is initially played. "), currentTotalTime);
if (EditorGUI.EndChangeCheck())
{
if (currentStepCount <= 0)
{
prewarmStepCount.uintValue = currentStepCount = 1u;
}
currentDeltaTime = currentTotalTime / currentStepCount;
prewarmDeltaTime.floatValue = currentDeltaTime;
resourceObject.ApplyModifiedProperties();
}
EditorGUI.BeginChangeCheck();
currentStepCount = (uint)EditorGUILayout.IntField(EditorGUIUtility.TrTextContent("PreWarm Step Count", "Sets the number of simulation steps the prewarm should be broken down to. "), (int)currentStepCount);
if (EditorGUI.EndChangeCheck())
{
if (currentStepCount <= 0 && currentTotalTime != 0.0f)
{
prewarmStepCount.uintValue = currentStepCount = 1;
}
currentDeltaTime = currentTotalTime == 0.0f ? 0.0f : currentTotalTime / currentStepCount;
prewarmDeltaTime.floatValue = currentDeltaTime;
prewarmStepCount.uintValue = currentStepCount;
resourceObject.ApplyModifiedProperties();
}
EditorGUI.BeginChangeCheck();
currentDeltaTime = EditorGUILayout.FloatField(EditorGUIUtility.TrTextContent("PreWarm Delta Time", "Sets the time in seconds for each step to achieve the desired total prewarm time."), currentDeltaTime);
if (EditorGUI.EndChangeCheck())
{
if (currentDeltaTime < k_MinimalCommonDeltaTime)
{
prewarmDeltaTime.floatValue = currentDeltaTime = k_MinimalCommonDeltaTime;
}
if (currentDeltaTime > currentTotalTime)
{
currentTotalTime = currentDeltaTime;
}
if (currentTotalTime != 0.0f)
{
var candidateStepCount_A = (uint)Mathf.FloorToInt(currentTotalTime / currentDeltaTime);
var candidateStepCount_B = (uint)Mathf.RoundToInt(currentTotalTime / currentDeltaTime);
var totalTime_A = currentDeltaTime * candidateStepCount_A;
var totalTime_B = currentDeltaTime * candidateStepCount_B;
if (Mathf.Abs(totalTime_A - currentTotalTime) < Mathf.Abs(totalTime_B - currentTotalTime))
{
currentStepCount = candidateStepCount_A;
}
else
{
currentStepCount = candidateStepCount_B;
}
prewarmStepCount.uintValue = currentStepCount;
}
prewarmDeltaTime.floatValue = currentDeltaTime;
resourceObject.ApplyModifiedProperties();
}
}
else
{
//Multi selection case, can't resolve total time easily
EditorGUI.BeginChangeCheck();
EditorGUI.showMixedValue = prewarmStepCount.hasMultipleDifferentValues;
EditorGUILayout.PropertyField(prewarmStepCount, EditorGUIUtility.TrTextContent("PreWarm Step Count", "Sets the number of simulation steps the prewarm should be broken down to."));
EditorGUI.showMixedValue = prewarmDeltaTime.hasMultipleDifferentValues;
EditorGUILayout.PropertyField(prewarmDeltaTime, EditorGUIUtility.TrTextContent("PreWarm Delta Time", "Sets the time in seconds for each step to achieve the desired total prewarm time."));
if (EditorGUI.EndChangeCheck())
{
if (prewarmDeltaTime.floatValue < k_MinimalCommonDeltaTime)
prewarmDeltaTime.floatValue = k_MinimalCommonDeltaTime;
resourceObject.ApplyModifiedProperties();
}
}
}
public override void OnInspectorGUI()
{
resourceObject.Update();
GUI.enabled = AssetDatabase.IsOpenForEdit(this.target, StatusQueryOptions.UseCachedIfPossible);
VFXUpdateMode initialUpdateMode = (VFXUpdateMode)0;
bool? initialFixedDeltaTime = null;
bool? initialProcessEveryFrame = null;
bool? initialIgnoreGameTimeScale = null;
if (resourceUpdateModeProperty.hasMultipleDifferentValues)
{
var resourceUpdateModeProperties = resourceUpdateModeProperty.serializedObject.targetObjects
.Select(o => new SerializedObject(o)
.FindProperty(resourceUpdateModeProperty.propertyPath))
.ToArray(); //N.B.: This will create garbage
var allDeltaTime = resourceUpdateModeProperties.Select(o => ((VFXUpdateMode)o.intValue & VFXUpdateMode.DeltaTime) == VFXUpdateMode.DeltaTime)
.Distinct();
var allProcessEveryFrame = resourceUpdateModeProperties.Select(o => ((VFXUpdateMode)o.intValue & VFXUpdateMode.ExactFixedTimeStep) == VFXUpdateMode.ExactFixedTimeStep)
.Distinct();
var allIgnoreScale = resourceUpdateModeProperties.Select(o => ((VFXUpdateMode)o.intValue & VFXUpdateMode.IgnoreTimeScale) == VFXUpdateMode.IgnoreTimeScale)
.Distinct();
if (allDeltaTime.Count() == 1)
initialFixedDeltaTime = !allDeltaTime.First();
if (allProcessEveryFrame.Count() == 1)
initialProcessEveryFrame = allProcessEveryFrame.First();
if (allIgnoreScale.Count() == 1)
initialIgnoreGameTimeScale = allIgnoreScale.First();
}
else
{
initialUpdateMode = (VFXUpdateMode)resourceUpdateModeProperty.intValue;
initialFixedDeltaTime = !((initialUpdateMode & VFXUpdateMode.DeltaTime) == VFXUpdateMode.DeltaTime);
initialProcessEveryFrame = (initialUpdateMode & VFXUpdateMode.ExactFixedTimeStep) == VFXUpdateMode.ExactFixedTimeStep;
initialIgnoreGameTimeScale = (initialUpdateMode & VFXUpdateMode.IgnoreTimeScale) == VFXUpdateMode.IgnoreTimeScale;
}
EditorGUI.showMixedValue = !initialFixedDeltaTime.HasValue;
var deltaTimeContent = EditorGUIUtility.TrTextContent("Fixed Delta Time", "If enabled, use visual effect manager fixed delta time mode, otherwise, use the default Time.deltaTime.");
var processEveryFrameContent = EditorGUIUtility.TrTextContent("Exact Fixed Time", "Only relevant when using Fixed Delta Time. When enabled, several updates can be processed per frame (e.g.: if a frame is 10ms and the fixed frame rate is set to 5 ms, the effect will update twice with a 5ms deltaTime instead of once with a 10ms deltaTime). This method is expensive and should only be used for high-end scenarios.");
var ignoreTimeScaleContent = EditorGUIUtility.TrTextContent("Ignore Time Scale", "When enabled, the computed visual effect delta time ignores the game Time Scale value (Play Rate is still applied).");
EditorGUI.BeginChangeCheck();
VisualEffectEditor.ShowHeader(EditorGUIUtility.TrTextContent("Update mode"), false, false);
bool newFixedDeltaTime = EditorGUILayout.Toggle(deltaTimeContent, initialFixedDeltaTime ?? false);
bool newExactFixedTimeStep = false;
EditorGUI.showMixedValue = !initialProcessEveryFrame.HasValue;
EditorGUI.BeginDisabledGroup((!initialFixedDeltaTime.HasValue || !initialFixedDeltaTime.Value) && !resourceUpdateModeProperty.hasMultipleDifferentValues);
newExactFixedTimeStep = EditorGUILayout.Toggle(processEveryFrameContent, initialProcessEveryFrame ?? false);
EditorGUI.EndDisabledGroup();
EditorGUI.showMixedValue = !initialIgnoreGameTimeScale.HasValue;
bool newIgnoreTimeScale = EditorGUILayout.Toggle(ignoreTimeScaleContent, initialIgnoreGameTimeScale ?? false);
if (EditorGUI.EndChangeCheck())
{
if (!resourceUpdateModeProperty.hasMultipleDifferentValues)
{
var newUpdateMode = (VFXUpdateMode)0;
if (!newFixedDeltaTime)
newUpdateMode = newUpdateMode | VFXUpdateMode.DeltaTime;
if (newExactFixedTimeStep)
newUpdateMode = newUpdateMode | VFXUpdateMode.ExactFixedTimeStep;
if (newIgnoreTimeScale)
newUpdateMode = newUpdateMode | VFXUpdateMode.IgnoreTimeScale;
resourceUpdateModeProperty.intValue = (int)newUpdateMode;
resourceObject.ApplyModifiedProperties();
}
else
{
var resourceUpdateModeProperties = resourceUpdateModeProperty.serializedObject.targetObjects.Select(o => new SerializedObject(o).FindProperty(resourceUpdateModeProperty.propertyPath));
foreach (var property in resourceUpdateModeProperties)
{
var updateMode = (VFXUpdateMode)property.intValue;
if (initialFixedDeltaTime.HasValue)
{
if (!newFixedDeltaTime)
updateMode = updateMode | VFXUpdateMode.DeltaTime;
else
updateMode = updateMode & ~VFXUpdateMode.DeltaTime;
}
else
{
if (newFixedDeltaTime)
updateMode = updateMode & ~VFXUpdateMode.DeltaTime;
}
if (newExactFixedTimeStep)
updateMode = updateMode | VFXUpdateMode.ExactFixedTimeStep;
else if (initialProcessEveryFrame.HasValue)
updateMode = updateMode & ~VFXUpdateMode.ExactFixedTimeStep;
if (newIgnoreTimeScale)
updateMode = updateMode | VFXUpdateMode.IgnoreTimeScale;
else if (initialIgnoreGameTimeScale.HasValue)
updateMode = updateMode & ~VFXUpdateMode.IgnoreTimeScale;
property.intValue = (int)updateMode;
property.serializedObject.ApplyModifiedProperties();
}
}
}
VisualEffectAsset asset = (VisualEffectAsset)target;
VisualEffectResource resource = asset.GetResource();
//The following should be working, and works for newly created systems, but fails for old systems,
//due probably to incorrectly pasting the VFXData when creating them.
// bool hasAutomaticBoundsSystems = resource.GetOrCreateGraph().children
// .OfType<VFXDataParticle>().Any(d => d.boundsMode == BoundsSettingMode.Automatic);
bool hasAutomaticBoundsSystems = resource.GetOrCreateGraph().children
.OfType<VFXBasicInitialize>()
.Select(x => x.GetData())
.OfType<VFXDataParticle>()
.Any(x => x.boundsMode == BoundsSettingMode.Automatic);
using (new EditorGUI.DisabledScope(hasAutomaticBoundsSystems))
{
EditorGUILayout.BeginHorizontal();
EditorGUI.showMixedValue = cullingFlagsProperty.hasMultipleDifferentValues;
string forceSimulateTooltip = hasAutomaticBoundsSystems
? " When using systems with Bounds Mode set to Automatic, this has to be set to Always recompute bounds and simulate."
: "";
EditorGUILayout.PrefixLabel(EditorGUIUtility.TrTextContent("Culling Flags", "Specifies how the system recomputes its bounds and simulates when off-screen." + forceSimulateTooltip));
EditorGUI.BeginChangeCheck();
int newOption =
EditorGUILayout.Popup(
Array.IndexOf(k_CullingOptionsValue, (VFXCullingFlags)cullingFlagsProperty.intValue),
k_CullingOptionsContents);
if (EditorGUI.EndChangeCheck())
{
cullingFlagsProperty.intValue = (int)k_CullingOptionsValue[newOption];
resourceObject.ApplyModifiedProperties();
}
}
EditorGUILayout.EndHorizontal();
DrawInstancingGUI();
VisualEffectEditor.ShowHeader(EditorGUIUtility.TrTextContent("Initial state"), false, false);
if (prewarmDeltaTime != null && prewarmStepCount != null)
{
DisplayPrewarmInspectorGUI(resourceObject, prewarmDeltaTime, prewarmStepCount);
}
if (initialEventName != null)
{
EditorGUI.BeginChangeCheck();
EditorGUI.showMixedValue = initialEventName.hasMultipleDifferentValues;
EditorGUILayout.PropertyField(initialEventName, new GUIContent("Initial Event Name", "Sets the name of the event which triggers once the system is activated. Default: OnPlay."));
if (EditorGUI.EndChangeCheck())
{
resourceObject.ApplyModifiedProperties();
}
}
if (!serializedObject.isEditingMultipleObjects)
{
asset = (VisualEffectAsset)target;
resource = asset.GetResource();
m_OutputContexts.Clear();
m_OutputContexts.AddRange(resource.GetOrCreateGraph().children.OfType<IVFXSubRenderer>().OrderBy(t => t.vfxSystemSortPriority));
m_ReorderableList.DoLayoutList();
VisualEffectEditor.ShowHeader(EditorGUIUtility.TrTextContent("Shaders"), false, false);
string assetPath = AssetDatabase.GetAssetPath(asset);
UnityObject[] objects = AssetDatabase.LoadAllAssetsAtPath(assetPath);
string directory = Path.GetDirectoryName(assetPath) + "/" + VFXExternalShaderProcessor.k_ShaderDirectory + "/" + asset.name + "/";
foreach (var obj in objects)
{
if (obj is Material || obj is ComputeShader)
{
GUILayout.BeginHorizontal();
Rect r = GUILayoutUtility.GetRect(0, 18, GUILayout.ExpandWidth(true));
int buttonsWidth = VFXExternalShaderProcessor.allowExternalization ? 240 : 160;
int index = resource.GetShaderIndex(obj);
var shader = obj;
if (obj is Material) // Retrieve the shader from the material
shader = ((Material)(obj)).shader;
if (shader == null)
continue;
Rect labelR = r;
labelR.width -= buttonsWidth;
GUI.Label(labelR, shader.name.Replace('\n', ' '));
if (index >= 0)
{
if (VFXExternalShaderProcessor.allowExternalization && index < resource.GetShaderSourceCount())
{
string shaderSourceName = resource.GetShaderSourceName(index);
string externalPath = directory + shaderSourceName;
externalPath = directory + shaderSourceName.Replace('/', '_') + VFXExternalShaderProcessor.k_ShaderExt;
Rect buttonRect = r;
buttonRect.xMin = labelR.xMax;
buttonRect.width = 80;
labelR.width += 80;
if (System.IO.File.Exists(externalPath))
{
if (GUI.Button(buttonRect, "Reveal External"))
{
EditorUtility.RevealInFinder(externalPath);
}
}
else
{
if (GUI.Button(buttonRect, "Externalize"))
{
Directory.CreateDirectory(directory);
File.WriteAllText(externalPath, "//" + shaderSourceName + "," + index.ToString() + "\n//Don't delete the previous line or this one\n" + resource.GetShaderSource(index));
}
}
}
Rect buttonR = r;
buttonR.xMin = labelR.xMax;
buttonR.width = 110;
labelR.width += 110;
if (GUI.Button(buttonR, "Show Generated"))
{
resource.ShowGeneratedShaderFile(index);
}
}
Rect selectButtonR = r;
selectButtonR.xMin = labelR.xMax;
selectButtonR.width = 50;
if (GUI.Button(selectButtonR, "Select"))
{
Selection.activeObject = shader;
}
GUILayout.EndHorizontal();
}
}
}
GUI.enabled = false;
}
private void DrawInstancingGUI()
{
VisualEffectEditor.ShowHeader(k_InstancingContent, false, false);
EditorGUI.BeginChangeCheck();
VFXInstancingDisabledReason disabledReason = (VFXInstancingDisabledReason)instancingDisabledReasonProperty.intValue;
bool forceDisabled = disabledReason != VFXInstancingDisabledReason.None;
if (forceDisabled)
{
System.Text.StringBuilder reasonString = new System.Text.StringBuilder("Instancing not available:");
GetInstancingDisabledReasons(reasonString, disabledReason);
EditorGUILayout.HelpBox(reasonString.ToString(), MessageType.Info);
}
VFXInstancingMode instancingMode = forceDisabled ? VFXInstancingMode.Disabled : (VFXInstancingMode)instancingModeProperty.intValue;
EditorGUI.BeginDisabled(forceDisabled);
instancingMode = (VFXInstancingMode)EditorGUILayout.EnumPopup(k_InstancingModeContent, instancingMode);
EditorGUI.EndDisabled();
int instancingCapacity = instancingCapacityProperty.intValue;
if (instancingMode == VFXInstancingMode.Custom)
{
instancingCapacity = EditorGUILayout.DelayedIntField(k_InstancingCapacityContent, instancingCapacity);
}
if (EditorGUI.EndChangeCheck())
{
instancingModeProperty.intValue = (int)instancingMode;
instancingCapacityProperty.intValue = System.Math.Max(instancingCapacity, 1);
resourceObject.ApplyModifiedProperties();
}
}
void GetInstancingDisabledReasons(System.Text.StringBuilder reasonString, VFXInstancingDisabledReason disabledReasonMask)
{
if (disabledReasonMask == VFXInstancingDisabledReason.Unknown)
{
GetInstancingDisabledReason(reasonString, VFXInstancingDisabledReason.Unknown);
}
else
{
Enum.GetValues(typeof(VFXInstancingDisabledReason))
.Cast<VFXInstancingDisabledReason>()
.Where(x => x != VFXInstancingDisabledReason.None && disabledReasonMask.HasFlag(x))
.ToList()
.ForEach(x => GetInstancingDisabledReason(reasonString, x));
}
}
void GetInstancingDisabledReason(System.Text.StringBuilder reasonString, VFXInstancingDisabledReason disabledReasonFlag)
{
reasonString.AppendLine();
reasonString.Append("- ");
Type type = disabledReasonFlag.GetType();
var memberInfo = type.GetMember(type.GetEnumName(disabledReasonFlag));
var descriptionAttribute = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() as DescriptionAttribute;
reasonString.Append(descriptionAttribute?.Description ?? disabledReasonFlag.ToString());
}
}
static class VFXPreviewGUI
{
static int sliderHash = "Slider".GetHashCode();
public static bool TryDrag2D(ref Vector2 scrollPosition, Rect position)
{
int id = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
Event evt = Event.current;
switch (evt.GetTypeForControl(id))
{
case EventType.MouseDown:
if (position.Contains(evt.mousePosition) && position.width > 50)
{
GUIUtility.hotControl = id;
evt.Use();
EditorGUIUtility.SetWantsMouseJumping(1);
}
return false;
case EventType.MouseDrag:
if (GUIUtility.hotControl == id)
{
scrollPosition -= -evt.delta * (evt.shift ? 3 : 1) / Mathf.Min(position.width, position.height) * 140.0f;
scrollPosition.y = Mathf.Clamp(scrollPosition.y, -90, 90);
evt.Use();
GUI.changed = true;
}
return true;
case EventType.MouseUp:
if (GUIUtility.hotControl == id)
GUIUtility.hotControl = 0;
EditorGUIUtility.SetWantsMouseJumping(0);
return false;
}
return false;
}
}