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.
 
 
 
 

391 lines
14 KiB

using System.Collections.Generic;
using System.IO;
using UnityEditor.VFX.Block;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.UIElements;
namespace UnityEditor.VFX.UI
{
class VFXContextProfilerUI : VFXAnchoredProfilerUI
{
public VFXContextProfilerUI(VFXContextUI contextUI)
{
m_ContextUI = contextUI;
controller = contextUI.controller;
controller.RegisterHandler(this);
if(!m_ContextUI.selected)
AddToClassList(k_HiddenStyleClass);
title = controller.model.name.Split(" ")[0];
AddToClassList("VFXContextProfiler");
AnchorTo(m_ContextUI);
m_TextureInfosLabels = new List<SlotLabel>();
m_TextureHashSet = new HashSet<Texture>();
}
private VFXContextUI m_ContextUI;
internal VFXContextUI contextUI => m_ContextUI;
private Label m_ContextAggregateGPUTiming;
public struct SlotLabel
{
public SlotLabel(VFXSlot slot, string text)
{
this.slot = slot;
this.label = new Label(text) { name = "texture-slot" };
}
public VFXSlot slot;
public Label label;
}
private Foldout m_TextureUsageFoldout;
private List<SlotLabel> m_TextureInfosLabels;
private HashSet<Texture> m_TextureHashSet;
private Label m_InfoBadge;
private VisualElement m_PositionBadge;
private VisualElement m_RotationBadge;
private VisualElement m_AgeBadge;
public override bool isCollapsed => ClassListContains(k_HiddenStyleClass);
void CollectAllTextureSlotsRecursive(IVFXSlotContainer slotContainer, HashSet<VFXSlot> allLinkedSlots)
{
foreach (var inputSlot in slotContainer.inputSlots)
{
if(inputSlot is VFXSlotTexture2D or VFXSlotTexture3D or VFXSlotTextureCube or VFXSlotTexture2DArray or VFXSlotTextureCubeArray )
allLinkedSlots.Add(inputSlot);
foreach (var linkedSlot in inputSlot.LinkedSlots)
{
CollectAllTextureSlotsRecursive(linkedSlot.owner, allLinkedSlots);
}
}
}
internal static string GetTextureInformationString(Texture texture)
{
string memorySize = EditorUtility.FormatBytes(TextureUtil.GetStorageMemorySizeLong(texture));
string textureInfo;
if (texture is Texture3D texture3D)
{
textureInfo = $"{texture3D.height}x{texture3D.width}x{texture3D.depth} - {memorySize}";
}
else
{
textureInfo = $"{texture.height}x{texture.width} - {memorySize}";
}
const int kMaxTotalLength = 35;
int lengthLeftForName = kMaxTotalLength - textureInfo.Length;
string textureName;
if (lengthLeftForName > texture.name.Length)
textureName = texture.name;
else
{
int halfLength = lengthLeftForName / 2;
textureName = $"{texture.name.Substring(0, halfLength)}...{texture.name.Substring(texture.name.Length - halfLength, halfLength)}";
}
return $"{textureName} - {textureInfo}";
}
internal List<SlotLabel> GetTextureSlotLabels()
{
return m_TextureInfosLabels;
}
private void RegisterTextureLabel(VFXSlot slot)
{
Texture texture = slot.value as Texture;
m_TextureInfosLabels.Add( new SlotLabel(slot, texture != null ? GetTextureInformationString(texture) : ""));
}
public override void OnUnselected()
{
base.OnUnselected();
if(!m_Locked)
AddToClassList(k_HiddenStyleClass);
}
public override void AnchorTo(VFXContextUI target)
{
base.AnchorTo(target);
target.onSelectionDelegate += OnContextSelection;
}
public override void Detach()
{
base.Detach();
m_ContextUI.onSelectionDelegate -= OnContextSelection;
}
internal void RepositionPanel()
{
RepositionPanel(m_ContextUI);
}
public override void ForceExpand()
{
RemoveFromClassList(k_HiddenStyleClass);
m_Foldout.value = true;
}
public override void ForceClose()
{
AddToClassList(k_HiddenStyleClass);
m_Foldout.value = false;
}
void OnContextSelection(bool isSelected)
{
if (isSelected)
{
RemoveFromClassList(k_HiddenStyleClass);
m_Foldout.value = true;
}
else
{
if(!m_Locked)
AddToClassList(k_HiddenStyleClass);
}
}
protected override void SetupStatsLayout()
{
if (attachedComponent == null)
return;
if (m_ProfilingGPUItemLeaves != null)
{
ClearContent();
m_ProfilingGPUItemLeaves.Clear();
}
else
{
m_ProfilingGPUItemLeaves = new List<VFXProfilingBoard.ProfilingItemLeaf>();
}
if(controller.model is VFXBasicSpawner)
SetupEventBadges();
if(controller.model is VFXBasicUpdate)
SetupImplicitUpdateBadges();
VFXSystemNames systemNames = controller.viewController.graph.systemNames;
string systemName = systemNames.GetUniqueSystemName(controller.model.GetData());
m_SystemName = systemName;
Foldout gpuTimeFoldout = new Foldout
{
name = "TimingGPU",
text = "Execution time (GPU)",
tooltip = "Execution time of the context on the GPU, in milliseconds. Number of dispatches/draw calls are in parenthesis."
};
gpuTimeFoldout.value = false;
m_ContextAggregateGPUTiming = AddLabelToFoldout(gpuTimeFoldout);
if (!m_SupportsGPURecorder)
{
m_ContextAggregateGPUTiming.text = "-";
gpuTimeFoldout.tooltip = kGpuRecorderNotSupportedMsg;
}
foreach (var taskId in controller.model.GetContextTaskIndices())
{
string markerName = attachedComponent.GetGPUTaskMarkerName(systemName, taskId.taskIndex);
if (string.IsNullOrEmpty(markerName))
continue;
VisualElement labelContainer = new VisualElement
{
style = { flexDirection = FlexDirection.Row, paddingTop = 2, paddingBottom = 2 }
};
Label taskLabel = new Label
{
name = markerName,
text = $"{taskId.taskName} ",
style = { flexGrow = 1 }
};
Label taskTimingLabel = new Label()
{
name = markerName,
style = { flexGrow = 0, alignSelf = Align.FlexEnd}
};
if (!m_SupportsGPURecorder)
{
taskTimingLabel.text = "-";
taskTimingLabel.tooltip = kGpuRecorderNotSupportedMsg;
}
labelContainer.Add(taskLabel);
labelContainer.Add(taskTimingLabel);
gpuTimeFoldout.Add(labelContainer);
var recorder = Recorder.Get(markerName);
VFXProfilingBoard.ProfilingItemLeaf leaf = new VFXProfilingBoard.ProfilingItemLeaf(recorder, taskTimingLabel);
m_ProfilingGPUItemLeaves.Add(leaf);
}
if(gpuTimeFoldout.childCount > 0)
AddContent(gpuTimeFoldout);
SetupTextureSection();
}
private void SetupTextureSection()
{
m_TextureInfosLabels.Clear();
HashSet<VFXSlot> textureSlots = new HashSet<VFXSlot>();
CollectAllTextureSlotsRecursive(controller.model, textureSlots);
foreach (var block in controller.model.children)
{
CollectAllTextureSlotsRecursive(block, textureSlots);
}
foreach (var slot in textureSlots)
{
RegisterTextureLabel(slot);
}
if (m_TextureInfosLabels.Count > 0)
{
m_TextureUsageFoldout = new Foldout
{
name = "TextureUsage",
text = "Texture Usage"
};
m_TextureUsageFoldout.value = false;
foreach (var textureInfoLabel in m_TextureInfosLabels)
{
m_TextureUsageFoldout.Add(textureInfoLabel.label);
}
AddContent(m_TextureUsageFoldout);
}
}
void SetupEventBadges()
{
VisualElement statusContainer = new VisualElement
{
style = { flexDirection = FlexDirection.Row, paddingTop = 2, paddingBottom = 2 }
};
m_InfoBadge = CreateBadge(null, "Indicates whether the last received event was a Start or a Stop event");
statusContainer.Add(m_InfoBadge);
AddContent(statusContainer);
}
void SetupImplicitUpdateBadges()
{
bool hasPosition = false;
bool hasRotation = false;
bool hasAge = false;
if (controller.model is VFXBasicUpdate basicUpdate)
{
foreach (var block in basicUpdate.activeChildrenWithImplicit)
{
if (block is EulerIntegration)
hasPosition = true;
if (block is AngularEulerIntegration)
hasRotation = true;
if (block is Age)
hasAge = true;
}
}
if (hasPosition || hasRotation || hasAge)
{
VisualElement statusContainer = new VisualElement
{
style = { flexDirection = FlexDirection.Row, paddingTop = 2, paddingBottom = 2 },
tooltip = "Attributes implicitly updated during the update context. They can be disabled in update context’s inspector."
};
if (hasPosition)
{
m_PositionBadge = CreateBadge("POSITION", "Particles positions are automatically updated based on their velocity attribute.");
m_PositionBadge.AddToClassList("position");
statusContainer.Add(m_PositionBadge);
}
if (hasRotation)
{
m_RotationBadge = CreateBadge("ROTATION", "Particles rotations are automatically updated based on their angular velocity attribute.");
m_RotationBadge.AddToClassList("rotation");
statusContainer.Add(m_RotationBadge);
}
if (hasAge)
{
m_AgeBadge = CreateBadge("AGE", "Particles ages are automatically incremented with deltaTime.");
m_AgeBadge.AddToClassList("age");
statusContainer.Add(m_AgeBadge);
}
AddContent(statusContainer);
}
}
internal override void UpdateDynamicValues()
{
base.UpdateDynamicValues();
if (m_ContextAggregateGPUTiming != null && m_SupportsGPURecorder)
{
float timeMs = NanoToMilli(GetGPUTimingAggregate());
m_ContextAggregateGPUTiming.text = $"{timeMs:F3} ms";
m_ContextAggregateGPUTiming.ClearClassList();
m_ContextAggregateGPUTiming.AddToClassList(ComputeHeatmapClass(timeMs, m_HeatmapRefValue));
}
if (m_TextureInfosLabels != null && m_TextureInfosLabels.Count > 0)
{
m_TextureHashSet.Clear();
foreach (var slotLabel in m_TextureInfosLabels)
{
Texture texture = slotLabel.slot.value as Texture;
if (m_TextureHashSet.Contains(texture) || texture == null)
{
slotLabel.label.style.display = DisplayStyle.None;
continue;
}
slotLabel.label.style.display = DisplayStyle.Flex;
string textureLabelText = GetTextureInformationString(texture);
m_TextureHashSet.Add(texture);
if (slotLabel.label.text != textureLabelText)
{
slotLabel.label.text = textureLabelText;
}
}
m_TextureUsageFoldout.style.display = m_TextureHashSet.Count == 0 ? DisplayStyle.None : DisplayStyle.Flex;
}
if (m_InfoBadge != null)
{
var spawnerState = m_AttachedComponent.GetSpawnSystemInfo(m_SystemName);
if (spawnerState.playing)
{
m_InfoBadge.text = "PLAYING";
m_InfoBadge.AddToClassList("play");
m_InfoBadge.RemoveFromClassList("stopped");
}
else
{
m_InfoBadge.text = "STOPPED";
m_InfoBadge.AddToClassList("stopped");
m_InfoBadge.RemoveFromClassList("play");
}
}
}
public long GetGPUTimingAggregate()
{
long aggregate = 0L;
foreach (var leaf in m_ProfilingGPUItemLeaves)
{
aggregate += leaf.average;
}
return aggregate;
}
}
}