Files
FlaxEngine/Source/Editor/Windows/Profiler/Memory.cs
2025-05-22 04:40:32 +02:00

282 lines
9.9 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
#if USE_PROFILER
using System;
using System.Collections.Generic;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Windows.Profiler
{
/// <summary>
/// The memory profiling mode focused on system memory allocations breakdown.
/// </summary>
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
internal sealed class Memory : ProfilerMode
{
private struct FrameData
{
public ProfilerMemory.GroupsArray Usage;
public ProfilerMemory.GroupsArray Peek;
public ProfilerMemory.GroupsArray Count;
}
private readonly SingleChart _nativeAllocationsChart;
private readonly SingleChart _managedAllocationsChart;
private readonly Table _table;
private SamplesBuffer<FrameData> _frames;
private List<Row> _tableRowsCache;
private string[] _groupNames;
private int[] _groupOrder;
public Memory()
: base("Memory")
{
// 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,
Pivot = Float2.Zero,
IsScrollable = true,
Parent = panel,
};
// Chart
_nativeAllocationsChart = new SingleChart
{
Title = "Native Memory Allocation",
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = layout,
};
_nativeAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;
_managedAllocationsChart = new SingleChart
{
Title = "Managed Memory Allocation",
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = layout,
};
_managedAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Table
var style = Style.Current;
var headerColor = style.LightBackground;
var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
{
new ColumnDefinition
{
UseExpandCollapseMode = true,
CellAlignment = TextAlignment.Near,
Title = "Group",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Usage",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellBytes,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Peek",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellBytes,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Count",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
},
Parent = layout,
};
_table.Splits = new[]
{
0.5f,
0.2f,
0.2f,
0.1f,
};
}
private string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount(Convert.ToUInt64(x));
}
/// <inheritdoc />
public override void Clear()
{
_nativeAllocationsChart.Clear();
_managedAllocationsChart.Clear();
_frames?.Clear();
}
/// <inheritdoc />
public override void Update(ref SharedUpdateData sharedData)
{
// Count memory allocated during last frame
int nativeMemoryAllocation = 0;
int managedMemoryAllocation = sharedData.ManagedMemoryAllocation;
var events = sharedData.GetEventsCPU();
var length = events?.Length ?? 0;
for (int i = 0; i < length; i++)
{
var ee = events[i].Events;
if (ee == null)
continue;
for (int j = 0; j < ee.Length; j++)
{
ref var e = ref ee[j];
if (e.NameStartsWith("ProfilerWindow"))
continue;
nativeMemoryAllocation += e.NativeMemoryAllocation;
managedMemoryAllocation += e.ManagedMemoryAllocation;
}
}
_nativeAllocationsChart.AddSample(nativeMemoryAllocation);
_managedAllocationsChart.AddSample(managedMemoryAllocation);
// Gather memory profiler stats for groups
var frame = new FrameData
{
Usage = ProfilerMemory.GetGroups(0),
Peek = ProfilerMemory.GetGroups(1),
Count = ProfilerMemory.GetGroups(2),
};
if (_frames == null)
_frames = new SamplesBuffer<FrameData>();
if (_groupNames == null)
_groupNames = ProfilerMemory.GetGroupNames();
_frames.Add(frame);
}
/// <inheritdoc />
public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
{
_nativeAllocationsChart.SelectedSampleIndex = selectedFrame;
_managedAllocationsChart.SelectedSampleIndex = selectedFrame;
UpdateTable(selectedFrame);
}
/// <inheritdoc />
public override void OnDestroy()
{
_tableRowsCache?.Clear();
_groupNames = null;
_groupOrder = null;
base.OnDestroy();
}
private void UpdateTable(int selectedFrame)
{
if (_frames == null)
return;
if (_tableRowsCache == null)
_tableRowsCache = new List<Row>();
_table.IsLayoutLocked = true;
RecycleTableRows(_table, _tableRowsCache);
UpdateTableInner(selectedFrame);
_table.UnlockChildrenRecursive();
_table.PerformLayout();
}
private unsafe void UpdateTableInner(int selectedFrame)
{
if (_frames.Count == 0)
return;
var frame = _frames.Get(selectedFrame);
var totalUage = frame.Usage.Values0[(int)ProfilerMemory.Groups.TotalTracked];
var totalPeek = frame.Peek.Values0[(int)ProfilerMemory.Groups.TotalTracked];
var totalCount = frame.Count.Values0[(int)ProfilerMemory.Groups.TotalTracked];
// Sort by memory size
if (_groupOrder == null)
_groupOrder = new int[(int)ProfilerMemory.Groups.MAX];
for (int i = 0; i < (int)ProfilerMemory.Groups.MAX; i++)
_groupOrder[i] = i;
Array.Sort(_groupOrder, (x, y) =>
{
var tmp = _frames.Get(selectedFrame);
return (int)(tmp.Usage.Values0[y] - tmp.Usage.Values0[x]);
});
// Add rows
var rowColor2 = Style.Current.Background * 1.4f;
for (int i = 0; i < (int)ProfilerMemory.Groups.MAX; i++)
{
var group = _groupOrder[i];
var groupUsage = frame.Usage.Values0[group];
if (groupUsage <= 0)
continue;
var groupPeek = frame.Peek.Values0[group];
var groupCount = frame.Count.Values0[group];
Row row;
if (_tableRowsCache.Count != 0)
{
var last = _tableRowsCache.Count - 1;
row = _tableRowsCache[last];
_tableRowsCache.RemoveAt(last);
}
else
{
row = new Row
{
Values = new object[4],
BackgroundColors = new Color[4],
};
}
{
// Group
row.Values[0] = _groupNames[group];
// Usage
row.Values[1] = groupUsage;
row.BackgroundColors[1] = Color.Red.AlphaMultiplied(Mathf.Min(1, (float)groupUsage / totalUage) * 0.5f);
// Peek
row.Values[2] = groupPeek;
row.BackgroundColors[2] = Color.Red.AlphaMultiplied(Mathf.Min(1, (float)groupPeek / totalPeek) * 0.5f);
// Count
row.Values[3] = groupCount;
row.BackgroundColors[3] = Color.Red.AlphaMultiplied(Mathf.Min(1, (float)groupCount / totalCount) * 0.5f);
}
row.Width = _table.Width;
row.BackgroundColor = i % 2 == 1 ? rowColor2 : Color.Transparent;
row.Parent = _table;
var useBackground = group != (int)ProfilerMemory.Groups.Total &&
group != (int)ProfilerMemory.Groups.TotalTracked &&
group != (int)ProfilerMemory.Groups.Malloc;
if (!useBackground)
{
for (int k = 1; k < row.BackgroundColors.Length; k++)
row.BackgroundColors[k] = Color.Transparent;
}
}
}
}
}
#endif