Add new memory profiler
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#if USE_PROFILER
|
#if USE_PROFILER
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FlaxEditor.GUI;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
@@ -13,9 +15,21 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
|
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
|
||||||
internal sealed class Memory : 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 _nativeAllocationsChart;
|
||||||
private readonly SingleChart _managedAllocationsChart;
|
private readonly SingleChart _managedAllocationsChart;
|
||||||
|
private readonly Table _table;
|
||||||
|
private SamplesBuffer<FrameData> _frames;
|
||||||
|
private List<Row> _tableRowsCache;
|
||||||
|
private string[] _groupNames;
|
||||||
|
private int[] _groupOrder;
|
||||||
|
|
||||||
public Memory()
|
public Memory()
|
||||||
: base("Memory")
|
: base("Memory")
|
||||||
{
|
{
|
||||||
@@ -50,6 +64,58 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
Parent = layout,
|
Parent = layout,
|
||||||
};
|
};
|
||||||
_managedAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
_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 />
|
/// <inheritdoc />
|
||||||
@@ -57,6 +123,7 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
{
|
{
|
||||||
_nativeAllocationsChart.Clear();
|
_nativeAllocationsChart.Clear();
|
||||||
_managedAllocationsChart.Clear();
|
_managedAllocationsChart.Clear();
|
||||||
|
_frames?.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -84,6 +151,19 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
|
|
||||||
_nativeAllocationsChart.AddSample(nativeMemoryAllocation);
|
_nativeAllocationsChart.AddSample(nativeMemoryAllocation);
|
||||||
_managedAllocationsChart.AddSample(managedMemoryAllocation);
|
_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 />
|
/// <inheritdoc />
|
||||||
@@ -91,6 +171,110 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
{
|
{
|
||||||
_nativeAllocationsChart.SelectedSampleIndex = selectedFrame;
|
_nativeAllocationsChart.SelectedSampleIndex = selectedFrame;
|
||||||
_managedAllocationsChart.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,6 +208,14 @@ public:
|
|||||||
return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
||||||
return StringUtils::Compare(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
return StringUtils::Compare(&(*this)[Length() - suffix.Length()], *suffix) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Contains(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
|
||||||
|
{
|
||||||
|
const int32 length = Length();
|
||||||
|
if (subStr == nullptr || length == 0)
|
||||||
|
return false;
|
||||||
|
return (searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(_data, subStr) : StringUtils::Find(_data, subStr)) != nullptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ Window* Engine::MainWindow = nullptr;
|
|||||||
|
|
||||||
int32 Engine::Main(const Char* cmdLine)
|
int32 Engine::Main(const Char* cmdLine)
|
||||||
{
|
{
|
||||||
|
#if COMPILE_WITH_PROFILER
|
||||||
|
extern void InitProfilerMemory(const Char*);
|
||||||
|
InitProfilerMemory(cmdLine);
|
||||||
|
#endif
|
||||||
|
PROFILE_MEM_BEGIN(Engine);
|
||||||
EngineImpl::CommandLine = cmdLine;
|
EngineImpl::CommandLine = cmdLine;
|
||||||
Globals::MainThreadID = Platform::GetCurrentThreadID();
|
Globals::MainThreadID = Platform::GetCurrentThreadID();
|
||||||
StartupTime = DateTime::Now();
|
StartupTime = DateTime::Now();
|
||||||
@@ -164,6 +169,7 @@ int32 Engine::Main(const Char* cmdLine)
|
|||||||
LOG_FLUSH();
|
LOG_FLUSH();
|
||||||
Time::Synchronize();
|
Time::Synchronize();
|
||||||
EngineImpl::IsReady = true;
|
EngineImpl::IsReady = true;
|
||||||
|
PROFILE_MEM_END();
|
||||||
|
|
||||||
// Main engine loop
|
// Main engine loop
|
||||||
const bool useSleep = true; // TODO: this should probably be a platform setting
|
const bool useSleep = true; // TODO: this should probably be a platform setting
|
||||||
@@ -204,6 +210,10 @@ int32 Engine::Main(const Char* cmdLine)
|
|||||||
{
|
{
|
||||||
PROFILE_CPU_NAMED("Platform.Tick");
|
PROFILE_CPU_NAMED("Platform.Tick");
|
||||||
Platform::Tick();
|
Platform::Tick();
|
||||||
|
#if COMPILE_WITH_PROFILER
|
||||||
|
extern void TickProfilerMemory();
|
||||||
|
TickProfilerMemory();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update game logic
|
// Update game logic
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "Engine/Core/Utilities.h"
|
#include "Engine/Core/Utilities.h"
|
||||||
#if COMPILE_WITH_PROFILER
|
#if COMPILE_WITH_PROFILER
|
||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
#include "Engine/Profiler/ProfilerCPU.h"
|
||||||
|
#include "Engine/Profiler/ProfilerMemory.h"
|
||||||
#endif
|
#endif
|
||||||
#include "Engine/Threading/Threading.h"
|
#include "Engine/Threading/Threading.h"
|
||||||
#include "Engine/Engine/CommandLine.h"
|
#include "Engine/Engine/CommandLine.h"
|
||||||
@@ -218,6 +219,10 @@ void PlatformBase::OnMemoryAlloc(void* ptr, uint64 size)
|
|||||||
tracy::Profiler::MemAllocCallstack(ptr, (size_t)size, 12, false);
|
tracy::Profiler::MemAllocCallstack(ptr, (size_t)size, 12, false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Register in memory profiler
|
||||||
|
if (ProfilerMemory::Enabled)
|
||||||
|
ProfilerMemory::OnMemoryAlloc(ptr, size);
|
||||||
|
|
||||||
// Register allocation during the current CPU event
|
// Register allocation during the current CPU event
|
||||||
auto thread = ProfilerCPU::GetCurrentThread();
|
auto thread = ProfilerCPU::GetCurrentThread();
|
||||||
if (thread != nullptr && thread->Buffer.GetCount() != 0)
|
if (thread != nullptr && thread->Buffer.GetCount() != 0)
|
||||||
@@ -235,6 +240,10 @@ void PlatformBase::OnMemoryFree(void* ptr)
|
|||||||
if (!ptr)
|
if (!ptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Register in memory profiler
|
||||||
|
if (ProfilerMemory::Enabled)
|
||||||
|
ProfilerMemory::OnMemoryFree(ptr);
|
||||||
|
|
||||||
#if TRACY_ENABLE_MEMORY
|
#if TRACY_ENABLE_MEMORY
|
||||||
// Track memory allocation in Tracy
|
// Track memory allocation in Tracy
|
||||||
tracy::Profiler::MemFree(ptr, false);
|
tracy::Profiler::MemFree(ptr, false);
|
||||||
@@ -372,6 +381,12 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er
|
|||||||
LOG(Error, "External Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(externalUsedPhysical), (int32)(100 * externalUsedPhysical / memoryStats.TotalPhysicalMemory));
|
LOG(Error, "External Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(externalUsedPhysical), (int32)(100 * externalUsedPhysical / memoryStats.TotalPhysicalMemory));
|
||||||
LOG(Error, "External Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(externalUsedVirtual), (int32)(100 * externalUsedVirtual / memoryStats.TotalVirtualMemory));
|
LOG(Error, "External Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(externalUsedVirtual), (int32)(100 * externalUsedVirtual / memoryStats.TotalVirtualMemory));
|
||||||
}
|
}
|
||||||
|
#if COMPILE_WITH_PROFILER
|
||||||
|
if (error == FatalErrorType::OutOfMemory || error == FatalErrorType::GPUOutOfMemory)
|
||||||
|
{
|
||||||
|
ProfilerMemory::Dump();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
if (Log::Logger::LogFilePath.HasChars())
|
if (Log::Logger::LogFilePath.HasChars())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dst">A pointer to the first operand. This value will be replaced with the result of the operation.</param>
|
/// <param name="dst">A pointer to the first operand. This value will be replaced with the result of the operation.</param>
|
||||||
/// <param name="value">The second operand.</param>
|
/// <param name="value">The second operand.</param>
|
||||||
/// <returns>The result value of the operation.</returns>
|
/// <returns>The original value of the dst parameter.</returns>
|
||||||
static int64 InterlockedAdd(int64 volatile* dst, int64 value) = delete;
|
static int64 InterlockedAdd(int64 volatile* dst, int64 value) = delete;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "ProfilerCPU.h"
|
#include "ProfilerCPU.h"
|
||||||
#include "ProfilerGPU.h"
|
#include "ProfilerGPU.h"
|
||||||
|
#include "ProfilerMemory.h"
|
||||||
|
|
||||||
#if COMPILE_WITH_PROFILER
|
#if COMPILE_WITH_PROFILER
|
||||||
|
|
||||||
|
|||||||
413
Source/Engine/Profiler/ProfilerMemory.cpp
Normal file
413
Source/Engine/Profiler/ProfilerMemory.cpp
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#if COMPILE_WITH_PROFILER
|
||||||
|
|
||||||
|
#include "ProfilerMemory.h"
|
||||||
|
#include "Engine/Core/Log.h"
|
||||||
|
#include "Engine/Core/Utilities.h"
|
||||||
|
#include "Engine/Core/Math/Math.h"
|
||||||
|
#include "Engine/Core/Types/StringBuilder.h"
|
||||||
|
#include "Engine/Core/Collections/Sorting.h"
|
||||||
|
#include "Engine/Core/Collections/Dictionary.h"
|
||||||
|
#include "Engine/Platform/MemoryStats.h"
|
||||||
|
#include "Engine/Platform/File.h"
|
||||||
|
#include "Engine/Scripting/Enums.h"
|
||||||
|
#include "Engine/Threading/ThreadLocal.h"
|
||||||
|
#include "Engine/Utilities/StringConverter.h"
|
||||||
|
|
||||||
|
#define GROUPS_COUNT (int32)ProfilerMemory::Groups::MAX
|
||||||
|
|
||||||
|
static_assert(GROUPS_COUNT <= MAX_uint8, "Fix memory profiler groups to fit a single byte.");
|
||||||
|
|
||||||
|
// Compact name storage.
|
||||||
|
struct GroupNameBuffer
|
||||||
|
{
|
||||||
|
Char Buffer[30];
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void Set(const T* str)
|
||||||
|
{
|
||||||
|
int32 max = StringUtils::Length(str), dst = 0;
|
||||||
|
char prev = 0;
|
||||||
|
for (int32 i = 0; i < max && dst < ARRAY_COUNT(Buffer) - 2; i++)
|
||||||
|
{
|
||||||
|
char cur = str[i];
|
||||||
|
if (StringUtils::IsUpper(cur) && StringUtils::IsLower(prev))
|
||||||
|
Buffer[dst++] = '/';
|
||||||
|
Buffer[dst++] = cur;
|
||||||
|
prev = cur;
|
||||||
|
}
|
||||||
|
Buffer[dst] = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compact groups stack container.
|
||||||
|
struct GroupStackData
|
||||||
|
{
|
||||||
|
uint8 Count : 7;
|
||||||
|
uint8 SkipRecursion : 1;
|
||||||
|
uint8 Stack[15];
|
||||||
|
|
||||||
|
FORCE_INLINE void Push(ProfilerMemory::Groups group)
|
||||||
|
{
|
||||||
|
if (Count < ARRAY_COUNT(Stack))
|
||||||
|
Count++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int a= 10;
|
||||||
|
}
|
||||||
|
Stack[Count - 1] = (uint8)group;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE void Pop()
|
||||||
|
{
|
||||||
|
if (Count > 0)
|
||||||
|
Count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE ProfilerMemory::Groups Peek() const
|
||||||
|
{
|
||||||
|
return Count > 0 ? (ProfilerMemory::Groups)Stack[Count - 1] : ProfilerMemory::Groups::Unknown;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct TIsPODType<GroupStackData>
|
||||||
|
{
|
||||||
|
enum { Value = true };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Memory allocation data for a specific pointer.
|
||||||
|
struct PointerData
|
||||||
|
{
|
||||||
|
uint32 Size;
|
||||||
|
uint8 Group;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct TIsPODType<PointerData>
|
||||||
|
{
|
||||||
|
enum { Value = true };
|
||||||
|
};
|
||||||
|
|
||||||
|
#define UPDATE_PEEK(group) Platform::AtomicStore(&GroupMemoryPeek[(int32)group], Math::Max(Platform::AtomicRead(&GroupMemory[(int32)group]), Platform::AtomicRead(&GroupMemoryPeek[(int32)group])))
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
alignas(16) volatile int64 GroupMemory[GROUPS_COUNT] = {};
|
||||||
|
alignas(16) volatile int64 GroupMemoryPeek[GROUPS_COUNT] = {};
|
||||||
|
alignas(16) volatile int64 GroupMemoryCount[GROUPS_COUNT] = {};
|
||||||
|
uint8 GroupParents[GROUPS_COUNT] = {};
|
||||||
|
ThreadLocal<GroupStackData> GroupStack;
|
||||||
|
GroupNameBuffer GroupNames[GROUPS_COUNT];
|
||||||
|
bool InitedNames = false;
|
||||||
|
CriticalSection PointersLocker;
|
||||||
|
Dictionary<void*, PointerData> Pointers;
|
||||||
|
|
||||||
|
void InitNames()
|
||||||
|
{
|
||||||
|
if (InitedNames)
|
||||||
|
return;
|
||||||
|
InitedNames = true;
|
||||||
|
for (int32 i = 0; i < GROUPS_COUNT; i++)
|
||||||
|
{
|
||||||
|
const char* name = ScriptingEnum::GetName((ProfilerMemory::Groups)i);
|
||||||
|
GroupNames[i].Set(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init constant memory
|
||||||
|
PROFILE_MEM_INC(ProgramSize, Platform::GetMemoryStats().ProgramSizeMemory);
|
||||||
|
UPDATE_PEEK(ProfilerMemory::Groups::ProgramSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dump(StringBuilder& output, const int32 maxCount)
|
||||||
|
{
|
||||||
|
InitNames();
|
||||||
|
|
||||||
|
// Sort groups
|
||||||
|
struct GroupInfo
|
||||||
|
{
|
||||||
|
ProfilerMemory::Groups Group;
|
||||||
|
int64 Size;
|
||||||
|
int64 Peek;
|
||||||
|
uint32 Count;
|
||||||
|
|
||||||
|
bool operator<(const GroupInfo& other) const
|
||||||
|
{
|
||||||
|
return Size > other.Size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
GroupInfo groups[GROUPS_COUNT];
|
||||||
|
for (int32 i = 0; i < GROUPS_COUNT; i++)
|
||||||
|
{
|
||||||
|
GroupInfo& group = groups[i];
|
||||||
|
group.Group = (ProfilerMemory::Groups)i;
|
||||||
|
group.Size = Platform::AtomicRead(&GroupMemory[i]);
|
||||||
|
group.Peek = Platform::AtomicRead(&GroupMemoryPeek[i]);
|
||||||
|
group.Count = (uint32)Platform::AtomicRead(&GroupMemoryCount[i]);
|
||||||
|
}
|
||||||
|
Sorting::QuickSort(groups, GROUPS_COUNT);
|
||||||
|
|
||||||
|
// Print groups
|
||||||
|
output.Append(TEXT("Memory profiler summary:")).AppendLine();
|
||||||
|
for (int32 i = 0; i < maxCount; i++)
|
||||||
|
{
|
||||||
|
const GroupInfo& group = groups[i];
|
||||||
|
if (group.Size == 0)
|
||||||
|
break;
|
||||||
|
const Char* name = GroupNames[(int32)group.Group].Buffer;
|
||||||
|
const String size = Utilities::BytesToText(group.Size);
|
||||||
|
const String peek = Utilities::BytesToText(group.Peek);
|
||||||
|
output.AppendFormat(TEXT("{:>30}: {:>11} (peek: {}, count: {})"), name, size.Get(), peek.Get(), group.Count);
|
||||||
|
output.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// Print count of memory allocs count per group
|
||||||
|
for (int32 i = 0; i < GROUPS_COUNT; i++)
|
||||||
|
{
|
||||||
|
GroupInfo& group = groups[i];
|
||||||
|
group.Group = (ProfilerMemory::Groups)i;
|
||||||
|
group.Size = 0;
|
||||||
|
}
|
||||||
|
PointersLocker.Lock();
|
||||||
|
for (auto& e : Pointers)
|
||||||
|
groups[e.Value.Group].Size++;
|
||||||
|
PointersLocker.Unlock();
|
||||||
|
Sorting::QuickSort(groups, GROUPS_COUNT);
|
||||||
|
output.Append(TEXT("Memory allocations count summary:")).AppendLine();
|
||||||
|
for (int32 i = 0; i < maxCount; i++)
|
||||||
|
{
|
||||||
|
const GroupInfo& group = groups[i];
|
||||||
|
if (group.Size == 0)
|
||||||
|
break;
|
||||||
|
const Char* name = GroupName[(int32)group.Group].Buffer;
|
||||||
|
output.AppendFormat(TEXT("{:>30}: {:>11}"), name, group.Size);
|
||||||
|
output.AppendLine();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE void AddGroupMemory(ProfilerMemory::Groups group, int64 add)
|
||||||
|
{
|
||||||
|
// Group itself
|
||||||
|
Platform::InterlockedAdd(&GroupMemory[(int32)group], add);
|
||||||
|
Platform::InterlockedIncrement(&GroupMemoryCount[(int32)group]);
|
||||||
|
UPDATE_PEEK(group);
|
||||||
|
|
||||||
|
// Total memory
|
||||||
|
Platform::InterlockedAdd(&GroupMemory[(int32)ProfilerMemory::Groups::TotalTracked], add);
|
||||||
|
Platform::InterlockedIncrement(&GroupMemoryCount[(int32)ProfilerMemory::Groups::TotalTracked]);
|
||||||
|
UPDATE_PEEK(ProfilerMemory::Groups::TotalTracked);
|
||||||
|
|
||||||
|
// Group hierarchy parents
|
||||||
|
uint8 parent = GroupParents[(int32)group];
|
||||||
|
while (parent != 0)
|
||||||
|
{
|
||||||
|
Platform::InterlockedAdd(&GroupMemory[parent], add);
|
||||||
|
Platform::InterlockedIncrement(&GroupMemoryCount[parent]);
|
||||||
|
UPDATE_PEEK(parent);
|
||||||
|
parent = GroupParents[parent];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE void SubGroupMemory(ProfilerMemory::Groups group, int64 add)
|
||||||
|
{
|
||||||
|
// Group itself
|
||||||
|
int64 value = Platform::InterlockedAdd(&GroupMemory[(int32)group], add);
|
||||||
|
Platform::InterlockedDecrement(&GroupMemoryCount[(int32)group]);
|
||||||
|
|
||||||
|
// Total memory
|
||||||
|
value = Platform::InterlockedAdd(&GroupMemory[(int32)ProfilerMemory::Groups::TotalTracked], add);
|
||||||
|
Platform::InterlockedDecrement(&GroupMemoryCount[(int32)ProfilerMemory::Groups::TotalTracked]);
|
||||||
|
|
||||||
|
// Group hierarchy parents
|
||||||
|
uint8 parent = GroupParents[(int32)group];
|
||||||
|
while (parent != 0)
|
||||||
|
{
|
||||||
|
value = Platform::InterlockedAdd(&GroupMemory[parent], add);
|
||||||
|
Platform::InterlockedDecrement(&GroupMemoryCount[parent]);
|
||||||
|
parent = GroupParents[parent];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitProfilerMemory(const Char* cmdLine)
|
||||||
|
{
|
||||||
|
// Check for command line option (memory profiling affects performance thus not active by default)
|
||||||
|
ProfilerMemory::Enabled = StringUtils::FindIgnoreCase(cmdLine, TEXT("-mem"));
|
||||||
|
|
||||||
|
// Init hierarchy
|
||||||
|
#define INIT_PARENT(parent, child) GroupParents[(int32)ProfilerMemory::Groups::child] = (uint8)ProfilerMemory::Groups::parent
|
||||||
|
INIT_PARENT(Graphics, GraphicsTextures);
|
||||||
|
INIT_PARENT(Graphics, GraphicsBuffers);
|
||||||
|
INIT_PARENT(Graphics, GraphicsMeshes);
|
||||||
|
INIT_PARENT(Graphics, GraphicsShaders);
|
||||||
|
INIT_PARENT(Graphics, GraphicsMaterials);
|
||||||
|
INIT_PARENT(Graphics, GraphicsCommands);
|
||||||
|
INIT_PARENT(Animations, AnimationsData);
|
||||||
|
INIT_PARENT(Content, ContentAssets);
|
||||||
|
INIT_PARENT(Content, ContentFiles);
|
||||||
|
#undef INIT_PARENT
|
||||||
|
}
|
||||||
|
|
||||||
|
void TickProfilerMemory()
|
||||||
|
{
|
||||||
|
// Update profiler memory
|
||||||
|
PointersLocker.Lock();
|
||||||
|
GroupMemory[(int32)ProfilerMemory::Groups::Profiler] =
|
||||||
|
sizeof(GroupMemory) + sizeof(GroupNames) + sizeof(GroupStack) +
|
||||||
|
Pointers.Capacity() * sizeof(Dictionary<void*, PointerData>::Bucket);
|
||||||
|
PointersLocker.Unlock();
|
||||||
|
|
||||||
|
// Get total system memory and update untracked amount
|
||||||
|
auto memory = Platform::GetProcessMemoryStats();
|
||||||
|
memory.UsedPhysicalMemory -= GroupMemory[(int32)ProfilerMemory::Groups::Profiler];
|
||||||
|
GroupMemory[(int32)ProfilerMemory::Groups::Total] = memory.UsedPhysicalMemory;
|
||||||
|
GroupMemory[(int32)ProfilerMemory::Groups::TotalUntracked] = Math::Max<int64>(memory.UsedPhysicalMemory - GroupMemory[(int32)ProfilerMemory::Groups::TotalTracked], 0);
|
||||||
|
|
||||||
|
// Update peeks
|
||||||
|
UPDATE_PEEK(ProfilerMemory::Groups::Profiler);
|
||||||
|
UPDATE_PEEK(ProfilerMemory::Groups::Total);
|
||||||
|
UPDATE_PEEK(ProfilerMemory::Groups::TotalUntracked);
|
||||||
|
GroupMemoryPeek[(int32)ProfilerMemory::Groups::Total] = Math::Max(GroupMemoryPeek[(int32)ProfilerMemory::Groups::Total], GroupMemoryPeek[(int32)ProfilerMemory::Groups::TotalTracked]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfilerMemory::Enabled = false;
|
||||||
|
|
||||||
|
void ProfilerMemory::IncrementGroup(Groups group, uint64 size)
|
||||||
|
{
|
||||||
|
AddGroupMemory(group, (int64)size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilerMemory::DecrementGroup(Groups group, uint64 size)
|
||||||
|
{
|
||||||
|
SubGroupMemory(group, -(int64)size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilerMemory::BeginGroup(Groups group)
|
||||||
|
{
|
||||||
|
auto& stack = GroupStack.Get();
|
||||||
|
stack.Push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilerMemory::EndGroup()
|
||||||
|
{
|
||||||
|
auto& stack = GroupStack.Get();
|
||||||
|
stack.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilerMemory::RenameGroup(Groups group, const StringView& name)
|
||||||
|
{
|
||||||
|
GroupNames[(int32)group].Set(name.Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<String> ProfilerMemory::GetGroupNames()
|
||||||
|
{
|
||||||
|
Array<String> result;
|
||||||
|
result.Resize((int32)Groups::MAX);
|
||||||
|
InitNames();
|
||||||
|
for (int32 i = 0; i < (int32)Groups::MAX; i++)
|
||||||
|
result[i] = GroupNames[i].Buffer;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerMemory::GroupsArray ProfilerMemory::GetGroups(int32 mode)
|
||||||
|
{
|
||||||
|
GroupsArray result;
|
||||||
|
Platform::MemoryClear(&result, sizeof(result));
|
||||||
|
static_assert(ARRAY_COUNT(result.Values) >= (int32)Groups::MAX, "Update group array size.");
|
||||||
|
InitNames();
|
||||||
|
if (mode == 0)
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < (int32)Groups::MAX; i++)
|
||||||
|
result.Values[i] = Platform::AtomicRead(&GroupMemory[i]);
|
||||||
|
}
|
||||||
|
else if (mode == 1)
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < (int32)Groups::MAX; i++)
|
||||||
|
result.Values[i] = Platform::AtomicRead(&GroupMemoryPeek[i]);
|
||||||
|
}
|
||||||
|
else if (mode == 2)
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < (int32)Groups::MAX; i++)
|
||||||
|
result.Values[i] = Platform::AtomicRead(&GroupMemoryCount[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilerMemory::Dump(const StringView& options)
|
||||||
|
{
|
||||||
|
bool file = options.Contains(TEXT("file"));
|
||||||
|
StringBuilder output;
|
||||||
|
int32 maxCount = 20;
|
||||||
|
if (file || options.Contains(TEXT("all")))
|
||||||
|
maxCount = MAX_int32;
|
||||||
|
::Dump(output, maxCount);
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
String path = String(StringUtils::GetDirectoryName(Log::Logger::LogFilePath)) / TEXT("Memory_") + DateTime::Now().ToFileNameString() + TEXT(".txt");
|
||||||
|
File::WriteAllText(path, output, Encoding::ANSI);
|
||||||
|
LOG(Info, "Saved to {}", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_STR(Info, output.ToStringView());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilerMemory::OnMemoryAlloc(void* ptr, uint64 size)
|
||||||
|
{
|
||||||
|
ASSERT_LOW_LAYER(Enabled && ptr);
|
||||||
|
auto& stack = GroupStack.Get();
|
||||||
|
if (stack.SkipRecursion)
|
||||||
|
return;
|
||||||
|
stack.SkipRecursion = true;
|
||||||
|
|
||||||
|
// Register pointer
|
||||||
|
PointerData ptrData;
|
||||||
|
ptrData.Size = size;
|
||||||
|
ptrData.Group = (uint8)stack.Peek();
|
||||||
|
PointersLocker.Lock();
|
||||||
|
Pointers[ptr] = ptrData;
|
||||||
|
PointersLocker.Unlock();
|
||||||
|
|
||||||
|
// Update group memory
|
||||||
|
const int64 add = (int64)size;
|
||||||
|
AddGroupMemory((Groups)ptrData.Group, add);
|
||||||
|
Platform::InterlockedAdd(&GroupMemory[(int32)ProfilerMemory::Groups::Malloc], add);
|
||||||
|
Platform::InterlockedIncrement(&GroupMemoryCount[(int32)ProfilerMemory::Groups::Malloc]);
|
||||||
|
UPDATE_PEEK(ProfilerMemory::Groups::Malloc);
|
||||||
|
|
||||||
|
stack.SkipRecursion = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilerMemory::OnMemoryFree(void* ptr)
|
||||||
|
{
|
||||||
|
ASSERT_LOW_LAYER(Enabled && ptr);
|
||||||
|
auto& stack = GroupStack.Get();
|
||||||
|
if (stack.SkipRecursion)
|
||||||
|
return;
|
||||||
|
stack.SkipRecursion = true;
|
||||||
|
|
||||||
|
// Find and remove pointer
|
||||||
|
PointerData ptrData;
|
||||||
|
PointersLocker.Lock();
|
||||||
|
auto it = Pointers.Find(ptr);
|
||||||
|
bool found = it.IsNotEnd();
|
||||||
|
if (found)
|
||||||
|
ptrData = it->Value;
|
||||||
|
Pointers.Remove(it);
|
||||||
|
PointersLocker.Unlock();
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
// Update group memory
|
||||||
|
const int64 add = -(int64)ptrData.Size;
|
||||||
|
SubGroupMemory((Groups)ptrData.Group, add);
|
||||||
|
Platform::InterlockedAdd(&GroupMemory[(int32)ProfilerMemory::Groups::Malloc], add);
|
||||||
|
Platform::InterlockedDecrement(&GroupMemoryCount[(int32)ProfilerMemory::Groups::Malloc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.SkipRecursion = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
258
Source/Engine/Profiler/ProfilerMemory.h
Normal file
258
Source/Engine/Profiler/ProfilerMemory.h
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/Platform/Platform.h"
|
||||||
|
|
||||||
|
#if COMPILE_WITH_PROFILER
|
||||||
|
|
||||||
|
#include "Engine/Core/Types/StringView.h"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides memory allocations collecting utilities.
|
||||||
|
/// </summary>
|
||||||
|
API_CLASS(Static) class FLAXENGINE_API ProfilerMemory
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(ProfilerMemory);
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// List of different memory categories used to track and analyze memory allocations specific to a certain engine system.
|
||||||
|
/// </summary>
|
||||||
|
API_ENUM() enum class Groups : uint8
|
||||||
|
{
|
||||||
|
// Not categorized.
|
||||||
|
Unknown,
|
||||||
|
// Total used system memory (reported by platform).
|
||||||
|
Total,
|
||||||
|
// Total amount of tracked memory (by ProfilerMemory tool).
|
||||||
|
TotalTracked,
|
||||||
|
// Total amount of untracked memory (gap between total system memory usage and tracked memory size).
|
||||||
|
TotalUntracked,
|
||||||
|
// Initial memory used by program upon startup (eg. executable size, static variables).
|
||||||
|
ProgramSize,
|
||||||
|
// Total memory allocated via malloc.
|
||||||
|
Malloc,
|
||||||
|
// General purpose engine memory.
|
||||||
|
Engine,
|
||||||
|
// Profiling tool memory overhead.
|
||||||
|
Profiler,
|
||||||
|
|
||||||
|
// Total graphics memory usage.
|
||||||
|
Graphics,
|
||||||
|
// Total textures memory usage.
|
||||||
|
GraphicsTextures,
|
||||||
|
// Total buffers memory usage.
|
||||||
|
GraphicsBuffers,
|
||||||
|
// Total meshes memory usage (vertex and idnex buffers allocated by models).
|
||||||
|
GraphicsMeshes,
|
||||||
|
// Totoal shaders memory usage (shaders bytecode, PSOs data).
|
||||||
|
GraphicsShaders,
|
||||||
|
// Totoal materials memory usage (constant buffers, parameters data).
|
||||||
|
GraphicsMaterials,
|
||||||
|
// Totoal command buffers memory usage (draw lists, constants uploads, ring buffer allocators).
|
||||||
|
GraphicsCommands,
|
||||||
|
|
||||||
|
// Total Artificial Intelligence systems memory usage (eg. Behavior Trees).
|
||||||
|
AI,
|
||||||
|
|
||||||
|
// Total animations system memory usage.
|
||||||
|
Animations,
|
||||||
|
// Total animation data memory usage (curves, events, keyframes, graphs, etc.).
|
||||||
|
AnimationsData,
|
||||||
|
|
||||||
|
// Total autio system memory.
|
||||||
|
Audio,
|
||||||
|
|
||||||
|
// Total content system memory usage.
|
||||||
|
Content,
|
||||||
|
// Total general purpose memory allocated by assets.
|
||||||
|
ContentAssets,
|
||||||
|
// Total memory used by content files buffers (file reading and streaming buffers).
|
||||||
|
ContentFiles,
|
||||||
|
// Total memory used by content streaming system (internals).
|
||||||
|
ContentStreaming,
|
||||||
|
|
||||||
|
// Total memory allocated by input system.
|
||||||
|
Input,
|
||||||
|
|
||||||
|
// Total memory allocated by scene objects.
|
||||||
|
Level,
|
||||||
|
|
||||||
|
// Total localization system memory.
|
||||||
|
Localization,
|
||||||
|
|
||||||
|
// Total navigation system memory.
|
||||||
|
Navigation,
|
||||||
|
|
||||||
|
// Total networking system memory.
|
||||||
|
Networking,
|
||||||
|
|
||||||
|
// Total particles memory (loaded assets, particles buffers and instance parameters).
|
||||||
|
Particles,
|
||||||
|
|
||||||
|
// Total physics memory.
|
||||||
|
Physics,
|
||||||
|
|
||||||
|
// Total scripting memory allocated by game.
|
||||||
|
Scripting,
|
||||||
|
|
||||||
|
// Total User Interface components memory.
|
||||||
|
UI,
|
||||||
|
|
||||||
|
// Total video system memory (video file data, frame buffers, GPU images and any audio buffers used by video playback).
|
||||||
|
Video,
|
||||||
|
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame0,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame1,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame2,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame3,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame4,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame5,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame6,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame7,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame8,
|
||||||
|
// Custom game-specific memory tracking.
|
||||||
|
CustomGame9,
|
||||||
|
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin0,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin1,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin2,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin3,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin4,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin5,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin6,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin7,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin8,
|
||||||
|
// Custom plugin-specific memory tracking.
|
||||||
|
CustomPlugin9,
|
||||||
|
|
||||||
|
// Total editor-specific memory.
|
||||||
|
Editor,
|
||||||
|
|
||||||
|
MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The memory groups array wraper to avoid dynamic memory allocation.
|
||||||
|
/// </summary>
|
||||||
|
API_STRUCT(NoDefault) struct GroupsArray
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(GroupsArray);
|
||||||
|
|
||||||
|
// Values for each group
|
||||||
|
API_FIELD(NoArray) int64 Values[100];
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// Increments memory usage by a specific group.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="group">The group to update.</param>
|
||||||
|
/// <param name="size">The amount of memory allocated (in bytes).</param>
|
||||||
|
API_FUNCTION() static void IncrementGroup(Groups group, uint64 size);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decrements memory usage by a specific group.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="group">The group to update.</param>
|
||||||
|
/// <param name="size">The amount of memory freed (in bytes).</param>
|
||||||
|
API_FUNCTION() static void DecrementGroup(Groups group, uint64 size);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enters a new group context scope (by the current thread). Informs the profiler about context of any memory allocations within.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="group">The group to enter.</param>
|
||||||
|
API_FUNCTION() static void BeginGroup(Groups group);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Leaves the last group context scope (by the current thread).
|
||||||
|
/// </summary>
|
||||||
|
API_FUNCTION() static void EndGroup();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renames the group. Can be used for custom game/plugin groups naming.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="group">The group to update.</param>
|
||||||
|
/// <param name="name">The new name to set.</param>
|
||||||
|
API_FUNCTION() static void RenameGroup(Groups group, const StringView& name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the names of all groups (array matches Groups enums).
|
||||||
|
/// </summary>
|
||||||
|
API_FUNCTION() static Array<String, HeapAllocation> GetGroupNames();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the memory stats for all groups (array matches Groups enums).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mode">0 to get current memory, 1 to get peek memory, 2 to get current count.</param>
|
||||||
|
API_FUNCTION() static GroupsArray GetGroups(int32 mode = 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dumps the memory allocations stats (groupped).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">'all' to dump all groups, 'file' to dump info to a file (in Logs folder)</param>
|
||||||
|
API_FUNCTION(Attributes="DebugCommand") static void Dump(const StringView& options = StringView::Empty);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// The profiling tools usage flag. Can be used to disable profiler. Run engine with '-mem' command line to activate it from start.
|
||||||
|
/// </summary>
|
||||||
|
static bool Enabled;
|
||||||
|
|
||||||
|
static void OnMemoryAlloc(void* ptr, uint64 size);
|
||||||
|
static void OnMemoryFree(void* ptr);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// Helper structure used to call begin/end on group within single code block.
|
||||||
|
/// </summary>
|
||||||
|
struct GroupScope
|
||||||
|
{
|
||||||
|
FORCE_INLINE GroupScope(Groups group)
|
||||||
|
{
|
||||||
|
if (ProfilerMemory::Enabled)
|
||||||
|
ProfilerMemory::BeginGroup(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE ~GroupScope()
|
||||||
|
{
|
||||||
|
if (ProfilerMemory::Enabled)
|
||||||
|
ProfilerMemory::EndGroup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PROFILE_MEM_INC(group, size) ProfilerMemory::IncrementGroup(ProfilerMemory::Groups::group, size)
|
||||||
|
#define PROFILE_MEM_DEC(group, size) ProfilerMemory::DecrementGroup(ProfilerMemory::Groups::group, size)
|
||||||
|
#define PROFILE_MEM(group) ProfilerMemory::GroupScope ProfileMem(ProfilerMemory::Groups::group)
|
||||||
|
#define PROFILE_MEM_BEGIN(group) if (ProfilerMemory::Enabled) ProfilerMemory::BeginGroup(ProfilerMemory::Groups::group)
|
||||||
|
#define PROFILE_MEM_END() if (ProfilerMemory::Enabled) ProfilerMemory::EndGroup()
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// Empty macros for disabled profiler
|
||||||
|
#define PROFILE_MEM_INC(group, size)
|
||||||
|
#define PROFILE_MEM_DEC(group, size)
|
||||||
|
#define PROFILE_MEM(group)
|
||||||
|
#define PROFILE_MEM_BEGIN(group)
|
||||||
|
#define PROFILE_MEM_END()
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user