1) Added ForegroundViewport as a new color. It is used in the main game viewport (ViewportWidgetButton), and the viewport for rendering of particles and materials. It is needed because the default foreground in a Light theme is black, but black does not work well in a viewport. A new color seemed appropriate. 2) Fixed the profiler window to use the Foreground color in multiple text elements, instead of Color.White (or no default TitleColor). This includes the Row class, Asset class, SingleChart class, Timeline Class, and more. 3) Added a second theme/Style (DefaultLight) to include with the engine. It uses RGB float values because those were easier to transfer from the saved values that I had created (and they're easier for me to edit if necessary). I tried to emulate how the Default theme is created/loaded/etc as closely as possible.
293 lines
10 KiB
C#
293 lines
10 KiB
C#
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using FlaxEditor.GUI;
|
|
using FlaxEngine;
|
|
using FlaxEngine.Json;
|
|
using FlaxEngine.GUI;
|
|
using FlaxEditor.GUI.ContextMenu;
|
|
using System.Linq;
|
|
|
|
namespace FlaxEditor.Windows.Profiler
|
|
{
|
|
/// <summary>
|
|
/// The Assets profiling mode.
|
|
/// </summary>
|
|
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
|
|
internal sealed class Assets : ProfilerMode
|
|
{
|
|
private class Resource
|
|
{
|
|
public string Name;
|
|
public string TypeName;
|
|
public int ReferencesCount;
|
|
public ulong MemoryUsage;
|
|
public Guid AssetId;
|
|
}
|
|
|
|
private readonly SingleChart _memoryUsageChart;
|
|
private readonly Table _table;
|
|
private SamplesBuffer<Resource[]> _resources;
|
|
private List<ClickableRow> _tableRowsCache;
|
|
private Dictionary<Guid, Resource> _resourceCache;
|
|
private StringBuilder _stringBuilder;
|
|
|
|
public Assets()
|
|
: base("Assets")
|
|
{
|
|
// Layout
|
|
var panel = new Panel(ScrollBars.Vertical)
|
|
{
|
|
AnchorPreset = AnchorPresets.StretchAll,
|
|
Offsets = Margin.Zero,
|
|
Parent = this,
|
|
};
|
|
var layout = new VerticalPanel
|
|
{
|
|
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
|
Offsets = Margin.Zero,
|
|
IsScrollable = true,
|
|
Parent = panel,
|
|
};
|
|
|
|
// Chart
|
|
_memoryUsageChart = new SingleChart
|
|
{
|
|
Title = "Assets Memory Usage (CPU)",
|
|
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
|
|
Parent = layout,
|
|
};
|
|
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
|
|
|
// Table
|
|
var headerColor = Style.Current.LightBackground;
|
|
var textColor = Style.Current.Foreground;
|
|
_table = new Table
|
|
{
|
|
Columns = new[]
|
|
{
|
|
new ColumnDefinition
|
|
{
|
|
UseExpandCollapseMode = true,
|
|
CellAlignment = TextAlignment.Near,
|
|
Title = "Resource",
|
|
TitleBackgroundColor = headerColor,
|
|
TitleColor = textColor,
|
|
},
|
|
new ColumnDefinition
|
|
{
|
|
Title = "Type",
|
|
CellAlignment = TextAlignment.Center,
|
|
TitleBackgroundColor = headerColor,
|
|
TitleColor = textColor,
|
|
},
|
|
new ColumnDefinition
|
|
{
|
|
Title = "References",
|
|
TitleBackgroundColor = headerColor,
|
|
TitleColor = textColor,
|
|
},
|
|
new ColumnDefinition
|
|
{
|
|
Title = "Memory Usage",
|
|
TitleBackgroundColor = headerColor,
|
|
TitleColor = textColor,
|
|
FormatValue = v => Utilities.Utils.FormatBytesCount((ulong)v),
|
|
},
|
|
},
|
|
Parent = layout,
|
|
};
|
|
_table.Splits = new[]
|
|
{
|
|
0.6f,
|
|
0.2f,
|
|
0.08f,
|
|
0.12f,
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Clear()
|
|
{
|
|
_memoryUsageChart.Clear();
|
|
_resources?.Clear();
|
|
_resourceCache?.Clear();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Update(ref SharedUpdateData sharedData)
|
|
{
|
|
if (_resourceCache == null)
|
|
_resourceCache = new Dictionary<Guid, Resource>();
|
|
if (_stringBuilder == null)
|
|
_stringBuilder = new StringBuilder();
|
|
|
|
// Capture current assets usage info
|
|
var assets = FlaxEngine.Content.Assets;
|
|
var resources = new Resource[assets.Length];
|
|
ulong totalMemoryUsage = 0;
|
|
for (int i = 0; i < resources.Length; i++)
|
|
{
|
|
var asset = assets[i];
|
|
ref var resource = ref resources[i];
|
|
if (!asset)
|
|
continue;
|
|
|
|
// Try to reuse cached resource info
|
|
var assetId = asset.ID;
|
|
if (!_resourceCache.TryGetValue(assetId, out resource))
|
|
{
|
|
resource = new Resource
|
|
{
|
|
Name = asset.Path,
|
|
TypeName = asset.TypeName,
|
|
AssetId = assetId,
|
|
};
|
|
var typeNameEnding = asset.TypeName.LastIndexOf('.');
|
|
if (typeNameEnding != -1)
|
|
resource.TypeName = resource.TypeName.Substring(typeNameEnding + 1);
|
|
var assetItem = Editor.Instance.ContentDatabase.FindAsset(assetId);
|
|
if (assetItem != null)
|
|
{
|
|
resource.Name = assetItem.NamePath;
|
|
}
|
|
if (string.IsNullOrEmpty(resource.Name) && asset.IsVirtual)
|
|
resource.Name = "<virtual>";
|
|
_resourceCache.Add(assetId, resource);
|
|
}
|
|
|
|
resource.MemoryUsage = asset.MemoryUsage;
|
|
resource.ReferencesCount = asset.ReferencesCount;
|
|
totalMemoryUsage += resource.MemoryUsage;
|
|
}
|
|
_memoryUsageChart.AddSample((float)totalMemoryUsage);
|
|
if (_resources == null)
|
|
_resources = new SamplesBuffer<Resource[]>();
|
|
_resources.Add(resources);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
|
|
{
|
|
_memoryUsageChart.SelectedSampleIndex = selectedFrame;
|
|
|
|
if (_resources == null)
|
|
return;
|
|
if (_tableRowsCache == null)
|
|
_tableRowsCache = new List<ClickableRow>();
|
|
UpdateTable();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnDestroy()
|
|
{
|
|
_resources?.Clear();
|
|
_resourceCache?.Clear();
|
|
_tableRowsCache?.Clear();
|
|
_stringBuilder?.Clear();
|
|
|
|
base.OnDestroy();
|
|
}
|
|
|
|
private void UpdateTable()
|
|
{
|
|
_table.IsLayoutLocked = true;
|
|
int idx = 0;
|
|
while (_table.Children.Count > idx)
|
|
{
|
|
var child = _table.Children[idx];
|
|
if (child is ClickableRow row)
|
|
{
|
|
_tableRowsCache.Add(row);
|
|
child.Parent = null;
|
|
}
|
|
else
|
|
{
|
|
idx++;
|
|
}
|
|
}
|
|
_table.LockChildrenRecursive();
|
|
|
|
UpdateTableInner();
|
|
|
|
_table.UnlockChildrenRecursive();
|
|
_table.PerformLayout();
|
|
}
|
|
|
|
private void UpdateTableInner()
|
|
{
|
|
if (_resources.Count == 0)
|
|
return;
|
|
var resources = _resources.Get(_memoryUsageChart.SelectedSampleIndex);
|
|
if (resources == null || resources.Length == 0)
|
|
return;
|
|
var resourcesOrdered = resources.OrderByDescending(x => x.MemoryUsage);
|
|
|
|
// Add rows
|
|
var rowColor2 = Style.Current.Background * 1.4f;
|
|
int rowIndex = 0;
|
|
foreach (var e in resourcesOrdered)
|
|
{
|
|
ClickableRow row;
|
|
if (_tableRowsCache.Count != 0)
|
|
{
|
|
// Reuse row
|
|
var last = _tableRowsCache.Count - 1;
|
|
row = _tableRowsCache[last];
|
|
_tableRowsCache.RemoveAt(last);
|
|
}
|
|
else
|
|
{
|
|
// Allocate new row
|
|
row = new ClickableRow { Values = new object[4] };
|
|
row.RowDoubleClick = OnRowDoubleClick;
|
|
row.RowRightClick = OnRowRightClick;
|
|
}
|
|
|
|
// Setup row data
|
|
row.Tag = e.AssetId;
|
|
row.Values[0] = e.Name;
|
|
row.Values[1] = e.TypeName;
|
|
row.Values[2] = e.ReferencesCount;
|
|
row.Values[3] = e.MemoryUsage;
|
|
|
|
// Add row to the table
|
|
row.Width = _table.Width;
|
|
row.BackgroundColor = rowIndex % 2 == 0 ? rowColor2 : Color.Transparent;
|
|
row.Parent = _table;
|
|
rowIndex++;
|
|
}
|
|
}
|
|
|
|
private void OnRowDoubleClick(ClickableRow row)
|
|
{
|
|
var assetId = (Guid)row.Tag;
|
|
var assetItem = Editor.Instance.ContentDatabase.FindAsset(assetId);
|
|
if (assetItem != null)
|
|
Editor.Instance.ContentEditing.Open(assetItem);
|
|
}
|
|
|
|
private void OnRowRightClick(ClickableRow row)
|
|
{
|
|
var assetId = (Guid)row.Tag;
|
|
var assetItem = Editor.Instance.ContentDatabase.FindAsset(assetId);
|
|
if (assetItem != null)
|
|
{
|
|
var cm = new ContextMenu();
|
|
ContextMenuButton b;
|
|
b = cm.AddButton("Open", () => Editor.Instance.ContentEditing.Open(assetItem));
|
|
cm.AddButton("Show in content window", () => Editor.Instance.Windows.ContentWin.Select(assetItem));
|
|
cm.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(assetItem.Path)));
|
|
cm.AddButton("Select actors using this asset", () => Editor.Instance.SceneEditing.SelectActorsUsingAsset(assetItem.ID));
|
|
cm.AddButton("Show asset references graph", () => Editor.Instance.Windows.Open(new AssetReferencesGraphWindow(Editor.Instance, assetItem)));
|
|
cm.AddButton("Copy name", () => Clipboard.Text = assetItem.NamePath);
|
|
cm.AddButton("Copy path", () => Clipboard.Text = assetItem.Path);
|
|
cm.AddButton("Copy asset ID", () => Clipboard.Text = JsonSerializer.GetStringID(assetItem.ID));
|
|
cm.Show(row, row.PointFromScreen(Input.MouseScreenPosition));
|
|
}
|
|
}
|
|
}
|
|
}
|