diff --git a/Source/Editor/Windows/Profiler/Assets.cs b/Source/Editor/Windows/Profiler/Assets.cs
new file mode 100644
index 000000000..9ca0e844e
--- /dev/null
+++ b/Source/Editor/Windows/Profiler/Assets.cs
@@ -0,0 +1,293 @@
+// Copyright (c) 2012-2022 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;
+
+namespace FlaxEditor.Windows.Profiler
+{
+ ///
+ /// The Assets profiling mode.
+ ///
+ ///
+ internal sealed class Assets : ProfilerMode
+ {
+ private struct 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 _resources;
+ private List _tableRowsCache;
+ private Dictionary _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;
+ _table = new Table
+ {
+ Columns = new[]
+ {
+ new ColumnDefinition
+ {
+ UseExpandCollapseMode = true,
+ CellAlignment = TextAlignment.Near,
+ Title = "Resource",
+ TitleBackgroundColor = headerColor,
+ },
+ new ColumnDefinition
+ {
+ Title = "Type",
+ CellAlignment = TextAlignment.Center,
+ TitleBackgroundColor = headerColor,
+ },
+ new ColumnDefinition
+ {
+ Title = "References",
+ TitleBackgroundColor = headerColor,
+ },
+ new ColumnDefinition
+ {
+ Title = "Memory Usage",
+ TitleBackgroundColor = headerColor,
+ FormatValue = v => Utilities.Utils.FormatBytesCount((ulong)v),
+ },
+ },
+ Parent = layout,
+ };
+ _table.Splits = new[]
+ {
+ 0.6f,
+ 0.2f,
+ 0.08f,
+ 0.12f,
+ };
+ }
+
+ ///
+ public override void Clear()
+ {
+ _memoryUsageChart.Clear();
+ _resources?.Clear();
+ _resourceCache?.Clear();
+ }
+
+ ///
+ public override void Update(ref SharedUpdateData sharedData)
+ {
+ if (_resourceCache == null)
+ _resourceCache = new Dictionary();
+ if (_stringBuilder == null)
+ _stringBuilder = new StringBuilder();
+
+ // Capture current assets usage info
+ var assets = FlaxEngine.Content.Assets;
+ var sb = _stringBuilder;
+ var resources = new Resource[assets.Length];
+ var contentDatabase = Editor.Instance.ContentDatabase;
+ ulong totalMemoryUsage = 0;
+ for (int i = 0; i < resources.Length; i++)
+ {
+ var asset = assets[i];
+ if (!asset)
+ continue;
+
+ // Try to reuse cached resource info
+ var assetId = asset.ID;
+ if (!_resourceCache.TryGetValue(assetId, out var 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 = "";
+ _resourceCache.Add(assetId, resource);
+ }
+
+ resource.MemoryUsage = asset.MemoryUsage;
+ totalMemoryUsage += resource.MemoryUsage;
+ resource.ReferencesCount = asset.ReferencesCount;
+ resources[i] = resource;
+ }
+ _memoryUsageChart.AddSample((float)totalMemoryUsage);
+ Array.Sort(resources, SortResources);
+ if (_resources == null)
+ _resources = new SamplesBuffer();
+ _resources.Add(resources);
+ }
+
+ ///
+ public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
+ {
+ _memoryUsageChart.SelectedSampleIndex = selectedFrame;
+
+ if (_resources == null)
+ return;
+ if (_tableRowsCache == null)
+ _tableRowsCache = new List();
+ UpdateTable();
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ _resources?.Clear();
+ _resourceCache?.Clear();
+ _tableRowsCache?.Clear();
+ _stringBuilder?.Clear();
+
+ base.OnDestroy();
+ }
+
+ private static int SortResources(Resource a, Resource b)
+ {
+ return (int)(b.MemoryUsage - a.MemoryUsage);
+ }
+
+ 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;
+
+ // Add rows
+ var rowColor2 = Style.Current.Background * 1.4f;
+ for (int i = 0; i < resources.Length; i++)
+ {
+ ref var e = ref resources[i];
+
+ 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 = i % 2 == 0 ? rowColor2 : Color.Transparent;
+ row.Parent = _table;
+ }
+ }
+
+ 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));
+ }
+ }
+ }
+}
diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs
index 827cc674f..a40c38ea1 100644
--- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs
+++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs
@@ -195,6 +195,7 @@ namespace FlaxEditor.Windows.Profiler
AddMode(new GPU());
AddMode(new MemoryGPU());
AddMode(new Memory());
+ AddMode(new Assets());
AddMode(new Network());
// Init view
@@ -206,6 +207,8 @@ namespace FlaxEditor.Windows.Profiler
}
UpdateButtons();
+
+ ScriptsBuilder.ScriptsReloadEnd += Clear; // Prevent crashes if any of the profiler tabs has some scripting types cached (eg. asset type info)
}
///
diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp
index a7aeeab3d..e761dfd1b 100644
--- a/Source/Engine/Content/Asset.cpp
+++ b/Source/Engine/Content/Asset.cpp
@@ -313,6 +313,17 @@ bool Asset::ShouldDeleteFileOnUnload() const
#endif
+uint64 Asset::GetMemoryUsage() const
+{
+ uint64 result = sizeof(Asset);
+ Locker.Lock();
+ if (_loadingTask)
+ result += sizeof(ContentLoadTask);
+ result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
+ Locker.Unlock();
+ return result;
+}
+
void Asset::Reload()
{
// Virtual assets are memory-only so reloading them makes no sense
diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h
index 6039b6b3a..410dca8ab 100644
--- a/Source/Engine/Content/Asset.h
+++ b/Source/Engine/Content/Asset.h
@@ -128,14 +128,17 @@ public:
}
#if USE_EDITOR
-
///
/// Determines whether this asset was marked to be deleted on unload.
///
API_PROPERTY() bool ShouldDeleteFileOnUnload() const;
-
#endif
+ ///
+ /// Gets amount of CPU memory used by this resource (in bytes). It's a rough estimation. Memory may be fragmented, compressed or sub-allocated so the actual memory pressure from this resource may vary.
+ ///
+ API_PROPERTY() virtual uint64 GetMemoryUsage() const;
+
public:
///
/// Reloads the asset.
@@ -160,7 +163,6 @@ public:
virtual void CancelStreaming();
#if USE_EDITOR
-
///
/// Gets the asset references. Supported only in Editor.
///
@@ -184,7 +186,6 @@ public:
///
/// The collection of the asset ids referenced by this asset.
API_FUNCTION() Array GetReferences() const;
-
#endif
///
diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h
index 57d685031..d8ae9a5ea 100644
--- a/Source/Engine/Core/Delegate.h
+++ b/Source/Engine/Core/Delegate.h
@@ -557,6 +557,14 @@ public:
return count;
}
+ ///
+ /// Gets the current capacity of delegate table (amount of function to store before resizing).
+ ///
+ int32 Capacity() const
+ {
+ return (int32)Platform::AtomicRead((intptr volatile*)&_size);
+ }
+
///
/// Determines whether any function is binded.
///
diff --git a/Source/Engine/Streaming/Streaming.cpp b/Source/Engine/Streaming/Streaming.cpp
index f7ad33dc0..d11724fe8 100644
--- a/Source/Engine/Streaming/Streaming.cpp
+++ b/Source/Engine/Streaming/Streaming.cpp
@@ -304,6 +304,9 @@ GPUSampler* Streaming::GetTextureGroupSampler(int32 index)
if (!sampler)
{
sampler = GPUSampler::New();
+#if GPU_ENABLE_RESOURCE_NAMING
+ sampler->SetName(group.Name);
+#endif
sampler->Init(desc);
TextureGroupSamplers[index] = sampler;
}
@@ -316,6 +319,9 @@ GPUSampler* Streaming::GetTextureGroupSampler(int32 index)
if (!FallbackSampler)
{
FallbackSampler = GPUSampler::New();
+#if GPU_ENABLE_RESOURCE_NAMING
+ sampler->SetName(TEXT("FallbackSampler"));
+#endif
FallbackSampler->Init(GPUSamplerDescription::New(GPUSamplerFilter::Trilinear));
}
sampler = FallbackSampler;