using System; using System.Reflection; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.UIElements; using UnityEngine.VFX; using Object = System.Object; namespace UnityEditor.VFX.UI { class VFXUIDebug { public enum Modes { None, Efficiency, Alive } public enum Events { VFXPlayPause, VFXStep, VFXReset, VFXStop } class CurveContent : ImmediateModeElement { class VerticalBar { Mesh m_Mesh; public VerticalBar(float xPos) { m_Mesh = new Mesh(); m_Mesh.vertices = new Vector3[] { new Vector3(xPos, -1, 0), new Vector3(xPos, 1, 0) }; m_Mesh.SetIndices(new int[] { 0, 1 }, MeshTopology.Lines, 0); } public Mesh GetMesh() { return m_Mesh; } } class NormalizedCurve { int m_MaxPoints; Mesh m_Mesh; Vector3[] m_Points; public NormalizedCurve(int maxPoints) { if (maxPoints < 2) maxPoints = 2; m_MaxPoints = maxPoints; // line output m_Points = new Vector3[maxPoints]; var linesIndices = new int[2 * (maxPoints - 1)]; var step = 1.0f / (float)(maxPoints - 1); for (int i = 0; i < maxPoints - 1; ++i) { m_Points[i] = new Vector3(i * step, -99999999, 0); linesIndices[2 * i] = i; linesIndices[2 * i + 1] = i + 1; } m_Points[m_Points.Length - 1] = new Vector3(1, 0, 0); m_Mesh = new Mesh(); m_Mesh.vertices = m_Points; m_Mesh.SetIndices(linesIndices, MeshTopology.Lines, 0); } public Mesh GetMesh() { return m_Mesh; } public void AddPoint(float value) { m_Points = m_Mesh.vertices; for (int i = 1; i < m_MaxPoints; ++i) m_Points[i - 1].y = m_Points[i].y; m_Points[m_Points.Length - 1].y = value; m_Mesh.vertices = m_Points; } public float GetMax() { float max = m_Points[0].y; foreach (var point in m_Points) { if (max < point.y) max = point.y; } return max; } } class SwitchableCurve { int m_MaxPoints; Toggle m_Toggle; //Button m_MaxAlive; public NormalizedCurve curve { get; set; } public int id { get; set; } public Toggle toggle { get { return m_Toggle; } set { if (m_Toggle != value && m_Toggle != null) m_Toggle.UnregisterValueChangedCallback(ToggleValueChanged); m_Toggle = value; } } public SwitchableCurve(int id, int maxPoints, Toggle toggle) { m_MaxPoints = maxPoints; curve = new NormalizedCurve(m_MaxPoints); this.id = id; this.toggle = toggle; if (this.toggle != null) toggle.RegisterValueChangedCallback(ToggleValueChanged); } public void ResetCurve() { curve = new NormalizedCurve(m_MaxPoints); } void ToggleValueChanged(ChangeEvent evt) { if (evt.newValue == false) curve = new NormalizedCurve(m_MaxPoints); } } Material m_CurveMat; Material m_BarMat; VFXUIDebug m_DebugUI; int m_ClippingMatrixId; List m_VFXCurves; VerticalBar m_VerticalBar; List m_TimeBarsOffsets; int m_MaxPoints; float m_TimeBetweenDraw; bool m_Pause; bool m_Stopped; bool m_Step; bool m_ShouldDrawTimeBars = true; static readonly float s_TimeBarsInterval = 1; private static Func GetWorldClipRect() { var worldClipProp = typeof(VisualElement).GetMethod("get_worldClip", BindingFlags.NonPublic | BindingFlags.Instance); if (worldClipProp != null) { return delegate (VisualElement elt) { return (Rect)worldClipProp.Invoke(elt, null); }; } Debug.LogError("could not retrieve get_worldClip"); return delegate (VisualElement elt) { return new Rect(); }; } private static readonly Func k_BoxWorldclip = GetWorldClipRect(); public CurveContent(VFXUIDebug debugUI, int maxPoints, long timeBetweenDraw = 33) { m_DebugUI = debugUI; m_CurveMat = new Material(Shader.Find("Hidden/VFX/SystemInfo")); m_BarMat = new Material(Shader.Find("Hidden/VFX/TimeBar")); m_ClippingMatrixId = Shader.PropertyToID("_ClipMatrix"); m_MaxPoints = maxPoints; m_VFXCurves = new List(); m_VerticalBar = new VerticalBar(0); m_TimeBarsOffsets = new List(); m_LastTimeBarDrawTime = -2 * s_TimeBarsInterval; SetSamplingRate((long)timeBetweenDraw); } public void SetSamplingRate(long rate) { //schedule.Execute(MarkDirtyRepaint).Every(rate); m_TimeBetweenDraw = rate / 1000.0f; } public void OnVFXChange() { if (m_DebugUI.m_VFX != null) { m_Pause = m_Stopped = m_DebugUI.m_VFX.pause; if (m_DebugUI.m_CurrentMode == Modes.Efficiency || m_DebugUI.m_CurrentMode == Modes.Alive) { m_VFXCurves.Clear(); m_TimeBarsOffsets.Clear(); for (int i = 0; i < m_DebugUI.m_GpuSystems.Count(); ++i) { var toggle = m_DebugUI.m_SystemInfos[m_DebugUI.m_GpuSystems[i]][1] as Toggle; var switchableCurve = new SwitchableCurve(m_DebugUI.m_GpuSystems[i], m_MaxPoints, toggle); m_VFXCurves.Add(switchableCurve); } } } } public void Notify(Events e) { switch (e) { case Events.VFXPlayPause: m_Pause = !m_Pause; m_Stopped = false; break; case Events.VFXStep: m_Step = true; m_Pause = true; m_Stopped = false; break; case Events.VFXReset: m_Stopped = false; m_Pause = false; foreach (var curve in m_VFXCurves) curve.ResetCurve(); m_TimeBarsOffsets.Clear(); break; case Events.VFXStop: m_Pause = true; m_Stopped = true; foreach (var curve in m_VFXCurves) curve.ResetCurve(); m_TimeBarsOffsets.Clear(); break; default: break; } } public void SetDrawTimeBars(bool status) { m_ShouldDrawTimeBars = status; } Object GetCurvesData() { switch (m_DebugUI.m_CurrentMode) { case Modes.Efficiency: return null; case Modes.Alive: { float max = -1; foreach (var switchableCurve in m_VFXCurves) { max = Mathf.Max(switchableCurve.curve.GetMax(), max); } return max; } default: return null; } } void UpdateCurve(SwitchableCurve switchableCurve, Object data) { switch (m_DebugUI.m_CurrentMode) { case Modes.Efficiency: { var stat = m_DebugUI.m_VFX.GetParticleSystemInfo(switchableCurve.id); float efficiency = (float)stat.aliveCount / (float)stat.capacity; m_CurveMat.SetFloat("_OrdinateScale", 1.0f); switchableCurve.curve.AddPoint(efficiency); m_DebugUI.UpdateSystemInfoEntry(switchableCurve.id, stat); } break; case Modes.Alive: { var stat = m_DebugUI.m_VFX.GetParticleSystemInfo(switchableCurve.id); float maxAlive = (float)data; var superior2 = 1u << (int)Mathf.CeilToInt(Mathf.Log(maxAlive, 2.0f)); m_DebugUI.m_YaxisElts[1].text = (superior2 / 2).ToString(); m_DebugUI.m_YaxisElts[2].text = superior2.ToString(); m_CurveMat.SetFloat("_OrdinateScale", 1.0f / (float)superior2); switchableCurve.curve.AddPoint(stat.aliveCount); m_DebugUI.UpdateSystemInfoEntry(switchableCurve.id, stat); } break; default: break; } } float m_LastSampleTime; float m_LastTimeBarDrawTime; void DrawCurves() { if (m_Stopped) return; MarkDirtyRepaint(); // draw matrix var debugRect = m_DebugUI.m_DebugDrawingBox.worldBound; var clippedDebugRect = k_BoxWorldclip(m_DebugUI.m_DebugDrawingBox); var windowRect = panel.InternalGetGUIView().position; var trans = new Vector4(debugRect.x / windowRect.width, (windowRect.height - (debugRect.y + debugRect.height)) / windowRect.height, 0, 0); var scale = new Vector3(debugRect.width / windowRect.width, debugRect.height / windowRect.height, 0); // clip matrix var clippedScale = new Vector3(windowRect.width / clippedDebugRect.width, windowRect.height / clippedDebugRect.height, 0); var clippedTrans = new Vector3(-clippedDebugRect.x / clippedDebugRect.width, ((clippedDebugRect.y + clippedDebugRect.height) - windowRect.height) / clippedDebugRect.height); var baseChange = Matrix4x4.TRS(clippedTrans, Quaternion.identity, clippedScale); m_CurveMat.SetMatrix(m_ClippingMatrixId, baseChange); m_BarMat.SetMatrix(m_ClippingMatrixId, baseChange); // curves update var now = Time.time; bool shouldSample = (!m_Pause && (now - m_LastSampleTime > m_TimeBetweenDraw)) || (m_Pause && m_Step); m_Step = false; if (shouldSample) { m_LastSampleTime = now; } int i = 0; var curveData = GetCurvesData(); var TRS = Matrix4x4.TRS(trans, Quaternion.identity, scale); foreach (var vfxCurve in m_VFXCurves) { if (vfxCurve.toggle == null || vfxCurve.toggle.value == true) { if (shouldSample && m_DebugUI.m_VFX.HasSystem(vfxCurve.id)) { UpdateCurve(vfxCurve, curveData); } m_CurveMat.SetColor("_Color", GetColor(i)); m_CurveMat.SetPass(0); Graphics.DrawMeshNow(vfxCurve.curve.GetMesh(), TRS); } ++i; } // time bars creation if (shouldSample && (now - m_LastTimeBarDrawTime > s_TimeBarsInterval)) { m_LastTimeBarDrawTime = now; m_TimeBarsOffsets.Add(1); } // time bars update var xShift = 1.0f / (float)(m_MaxPoints - 1); Color timeBarColor = new Color(1, 0, 0, 0.5f).gamma; for (int j = 0; j < m_TimeBarsOffsets.Count(); ++j) { if (m_TimeBarsOffsets[j] < 0) { m_TimeBarsOffsets.RemoveAt(j); --j; continue; } m_BarMat.SetFloat("_AbscissaOffset", m_TimeBarsOffsets[j]); if (shouldSample) m_TimeBarsOffsets[j] -= xShift; if (m_ShouldDrawTimeBars) { m_BarMat.SetColor("_Color", timeBarColor); m_BarMat.SetPass(0); Graphics.DrawMeshNow(m_VerticalBar.GetMesh(), TRS); } } m_BarMat.SetFloat("_AbscissaOffset", 0); } protected override void ImmediateRepaint() { DrawCurves(); } } // graph characteristics VFXGraph m_Graph; VFXView m_View; VisualEffect m_VFX; List m_GpuSystems; // debug components Modes m_CurrentMode; VFXComponentBoard m_ComponentBoard; VisualElement m_DebugContainer; Button m_DebugButton; VisualElement m_SystemInfosContainer; Box m_DebugDrawingBox; CurveContent m_Curves; // VisualElement[] layout: // [0] container // [1] toggle // [2] system name // [3] alive // [4] max alive (Button or TextElement) // [5] efficiency Dictionary m_SystemInfos; // TextElement[] layout: // [0] bottom value // [1] mid value // [2] top value TextElement[] m_YaxisElts; public VFXUIDebug(VFXView view) { m_View = view; m_Graph = m_View.controller.graph; m_GpuSystems = new List(); } ~VFXUIDebug() { Clear(); m_View = null; m_VFX = null; m_GpuSystems = null; m_CurrentMode = Modes.None; } public Modes GetDebugMode() { return m_CurrentMode; } public void SetDebugMode(Modes mode, VFXComponentBoard componentBoard, bool force = false) { if (mode == m_CurrentMode && !force) return; Clear(); m_CurrentMode = mode; m_ComponentBoard = componentBoard; m_DebugContainer = m_ComponentBoard.Query("debug-modes-container"); m_DebugButton = m_ComponentBoard.Query