From 2dc404cbd36a2f4af8f5bb0a7a2723eefe7b800e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 22 May 2025 04:40:32 +0200 Subject: [PATCH 01/84] Add new memory profiler --- Source/Editor/Windows/Profiler/Memory.cs | 186 ++++++++- Source/Engine/Core/Types/StringView.h | 8 + Source/Engine/Engine/Engine.cpp | 10 + Source/Engine/Platform/Base/PlatformBase.cpp | 15 + Source/Engine/Platform/Base/PlatformBase.h | 2 +- Source/Engine/Profiler/Profiler.h | 1 + Source/Engine/Profiler/ProfilerMemory.cpp | 413 +++++++++++++++++++ Source/Engine/Profiler/ProfilerMemory.h | 258 ++++++++++++ 8 files changed, 891 insertions(+), 2 deletions(-) create mode 100644 Source/Engine/Profiler/ProfilerMemory.cpp create mode 100644 Source/Engine/Profiler/ProfilerMemory.h diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index d7bdc43af..f33bec4cc 100644 --- a/Source/Editor/Windows/Profiler/Memory.cs +++ b/Source/Editor/Windows/Profiler/Memory.cs @@ -2,6 +2,8 @@ #if USE_PROFILER using System; +using System.Collections.Generic; +using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; @@ -13,9 +15,21 @@ namespace FlaxEditor.Windows.Profiler /// 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 _frames; + private List _tableRowsCache; + private string[] _groupNames; + private int[] _groupOrder; + public Memory() : base("Memory") { @@ -50,6 +64,58 @@ namespace FlaxEditor.Windows.Profiler 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)); } /// @@ -57,6 +123,7 @@ namespace FlaxEditor.Windows.Profiler { _nativeAllocationsChart.Clear(); _managedAllocationsChart.Clear(); + _frames?.Clear(); } /// @@ -84,6 +151,19 @@ namespace FlaxEditor.Windows.Profiler _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(); + if (_groupNames == null) + _groupNames = ProfilerMemory.GetGroupNames(); + _frames.Add(frame); } /// @@ -91,6 +171,110 @@ namespace FlaxEditor.Windows.Profiler { _nativeAllocationsChart.SelectedSampleIndex = selectedFrame; _managedAllocationsChart.SelectedSampleIndex = selectedFrame; + + UpdateTable(selectedFrame); + } + + /// + 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(); + _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; + } + } } } } diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index 25d156c64..cbc221e7b 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -208,6 +208,14 @@ public: return StringUtils::CompareIgnoreCase(&(*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; + } }; /// diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 385a05554..288e0da11 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -79,6 +79,11 @@ Window* Engine::MainWindow = nullptr; 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; Globals::MainThreadID = Platform::GetCurrentThreadID(); StartupTime = DateTime::Now(); @@ -164,6 +169,7 @@ int32 Engine::Main(const Char* cmdLine) LOG_FLUSH(); Time::Synchronize(); EngineImpl::IsReady = true; + PROFILE_MEM_END(); // Main engine loop 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"); Platform::Tick(); +#if COMPILE_WITH_PROFILER + extern void TickProfilerMemory(); + TickProfilerMemory(); +#endif } // Update game logic diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 65f995967..9ba6b7bd6 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -17,6 +17,7 @@ #include "Engine/Core/Utilities.h" #if COMPILE_WITH_PROFILER #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #endif #include "Engine/Threading/Threading.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); #endif + // Register in memory profiler + if (ProfilerMemory::Enabled) + ProfilerMemory::OnMemoryAlloc(ptr, size); + // Register allocation during the current CPU event auto thread = ProfilerCPU::GetCurrentThread(); if (thread != nullptr && thread->Buffer.GetCount() != 0) @@ -235,6 +240,10 @@ void PlatformBase::OnMemoryFree(void* ptr) if (!ptr) return; + // Register in memory profiler + if (ProfilerMemory::Enabled) + ProfilerMemory::OnMemoryFree(ptr); + #if TRACY_ENABLE_MEMORY // Track memory allocation in Tracy 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 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()) { diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index fb2c68099..ff47e77a3 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -286,7 +286,7 @@ public: /// /// A pointer to the first operand. This value will be replaced with the result of the operation. /// The second operand. - /// The result value of the operation. + /// The original value of the dst parameter. static int64 InterlockedAdd(int64 volatile* dst, int64 value) = delete; /// diff --git a/Source/Engine/Profiler/Profiler.h b/Source/Engine/Profiler/Profiler.h index f0f7aad05..b80541719 100644 --- a/Source/Engine/Profiler/Profiler.h +++ b/Source/Engine/Profiler/Profiler.h @@ -4,6 +4,7 @@ #include "ProfilerCPU.h" #include "ProfilerGPU.h" +#include "ProfilerMemory.h" #if COMPILE_WITH_PROFILER diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp new file mode 100644 index 000000000..e617d712c --- /dev/null +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -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 + 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 +{ + enum { Value = true }; +}; + +// Memory allocation data for a specific pointer. +struct PointerData +{ + uint32 Size; + uint8 Group; +}; + +template<> +struct TIsPODType +{ + 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 GroupStack; + GroupNameBuffer GroupNames[GROUPS_COUNT]; + bool InitedNames = false; + CriticalSection PointersLocker; + Dictionary 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::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(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 ProfilerMemory::GetGroupNames() +{ + Array 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 diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h new file mode 100644 index 000000000..65ed5d9ab --- /dev/null +++ b/Source/Engine/Profiler/ProfilerMemory.h @@ -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" + +/// +/// Provides memory allocations collecting utilities. +/// +API_CLASS(Static) class FLAXENGINE_API ProfilerMemory +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(ProfilerMemory); +public: + /// + /// List of different memory categories used to track and analyze memory allocations specific to a certain engine system. + /// + 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 + }; + + /// + /// The memory groups array wraper to avoid dynamic memory allocation. + /// + API_STRUCT(NoDefault) struct GroupsArray + { + DECLARE_SCRIPTING_TYPE_MINIMAL(GroupsArray); + + // Values for each group + API_FIELD(NoArray) int64 Values[100]; + }; + +public: + /// + /// Increments memory usage by a specific group. + /// + /// The group to update. + /// The amount of memory allocated (in bytes). + API_FUNCTION() static void IncrementGroup(Groups group, uint64 size); + + /// + /// Decrements memory usage by a specific group. + /// + /// The group to update. + /// The amount of memory freed (in bytes). + API_FUNCTION() static void DecrementGroup(Groups group, uint64 size); + + /// + /// Enters a new group context scope (by the current thread). Informs the profiler about context of any memory allocations within. + /// + /// The group to enter. + API_FUNCTION() static void BeginGroup(Groups group); + + /// + /// Leaves the last group context scope (by the current thread). + /// + API_FUNCTION() static void EndGroup(); + + /// + /// Renames the group. Can be used for custom game/plugin groups naming. + /// + /// The group to update. + /// The new name to set. + API_FUNCTION() static void RenameGroup(Groups group, const StringView& name); + + /// + /// Gets the names of all groups (array matches Groups enums). + /// + API_FUNCTION() static Array GetGroupNames(); + + /// + /// Gets the memory stats for all groups (array matches Groups enums). + /// + /// 0 to get current memory, 1 to get peek memory, 2 to get current count. + API_FUNCTION() static GroupsArray GetGroups(int32 mode = 0); + + /// + /// Dumps the memory allocations stats (groupped). + /// + /// 'all' to dump all groups, 'file' to dump info to a file (in Logs folder) + API_FUNCTION(Attributes="DebugCommand") static void Dump(const StringView& options = StringView::Empty); + +public: + /// + /// The profiling tools usage flag. Can be used to disable profiler. Run engine with '-mem' command line to activate it from start. + /// + static bool Enabled; + + static void OnMemoryAlloc(void* ptr, uint64 size); + static void OnMemoryFree(void* ptr); + +public: + /// + /// Helper structure used to call begin/end on group within single code block. + /// + 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 From 9215f2662f929d99e15fffa0d9514ce2ff823190 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 22 May 2025 04:41:01 +0200 Subject: [PATCH 02/84] Add missing memory alloc profiling for virtual pages --- Source/Engine/Platform/Win32/Win32Platform.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index a61d7c36d..6b5eaa2ff 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -283,14 +283,23 @@ void* Win32Platform::AllocatePages(uint64 numPages, uint64 pageSize) { const uint64 numBytes = numPages * pageSize; #if PLATFORM_UWP - return VirtualAllocFromApp(nullptr, (SIZE_T)numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + void* ptr = VirtualAllocFromApp(nullptr, (SIZE_T)numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); #else - return VirtualAlloc(nullptr, (SIZE_T)numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + void* ptr = VirtualAlloc(nullptr, (SIZE_T)numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); #endif + if (!ptr) + OutOfMemory(); +#if COMPILE_WITH_PROFILER + OnMemoryAlloc(ptr, size); +#endif + return ptr; } void Win32Platform::FreePages(void* ptr) { +#if COMPILE_WITH_PROFILER + OnMemoryFree(ptr); +#endif VirtualFree(ptr, 0, MEM_RELEASE); } From 66dcfafa2ec69142667b4352a47b5ea09532cbff Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 22 May 2025 04:42:01 +0200 Subject: [PATCH 03/84] Fix Vulkan descriptor sets pooling --- Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp index 82bbd93d6..10a247a56 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp @@ -292,7 +292,7 @@ DescriptorPoolSetContainerVulkan* DescriptorPoolsManagerVulkan::AcquirePoolSetCo ScopeLock lock(_locker); for (auto* poolSet : _poolSets) { - if (poolSet->Refs == 0 && Engine::FrameCount - poolSet->LastFrameUsed > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT) + if (poolSet->Refs == 0 && Engine::FrameCount != poolSet->LastFrameUsed) { poolSet->LastFrameUsed = Engine::FrameCount; poolSet->Reset(); From 32bc73610fe4d40071c681d1d36128b798d9401e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 22 May 2025 04:45:12 +0200 Subject: [PATCH 04/84] Fix debug command type detection when it's used with argument --- Source/Engine/Debug/DebugCommands.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 5d94cf557..fcea938aa 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -438,6 +438,8 @@ void DebugCommands::InitAsync() DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command) { CommandFlags result = CommandFlags::None; + if (command.FindLast(' ') != -1) + command = command.Left(command.Find(' ')); // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush) String commandCopy = command; command = commandCopy; From c639a3103cc4de82c2222fd781ddd45d323d3164 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 22 May 2025 04:47:01 +0200 Subject: [PATCH 05/84] Add memory profiling events to the main areas of the engine --- Source/Editor/Cooker/GameCooker.cpp | 8 +++++++ .../CustomEditors/CustomEditorsUtil.cpp | 5 ++++ Source/Editor/Editor.cpp | 10 ++++++++ Source/Editor/ProjectInfo.cpp | 2 ++ Source/Editor/Scripting/CodeEditor.cpp | 3 +++ Source/Editor/Scripting/ScriptsBuilder.cpp | 5 ++++ .../Utilities/ViewportIconsRenderer.cpp | 2 ++ Source/Engine/AI/Behavior.cpp | 3 +++ Source/Engine/AI/BehaviorKnowledge.cpp | 2 ++ Source/Engine/AI/BehaviorTree.cpp | 2 ++ Source/Engine/Animations/Animations.cpp | 4 ++++ .../SceneAnimations/SceneAnimation.cpp | 2 ++ .../SceneAnimations/SceneAnimationPlayer.cpp | 3 +++ Source/Engine/Audio/Audio.cpp | 3 +++ Source/Engine/Audio/AudioClip.cpp | 2 ++ .../Engine/Audio/OpenAL/AudioBackendOAL.cpp | 6 +++++ Source/Engine/Content/Asset.cpp | 1 + Source/Engine/Content/Assets/Animation.cpp | 2 ++ .../Engine/Content/Assets/AnimationGraph.cpp | 3 +++ .../Content/Assets/AnimationGraphFunction.cpp | 2 ++ Source/Engine/Content/Assets/Material.cpp | 4 ++++ Source/Engine/Content/Assets/Model.cpp | 6 +++++ Source/Engine/Content/Assets/ModelBase.cpp | 4 ++++ Source/Engine/Content/Assets/SkinnedModel.cpp | 7 ++++++ Source/Engine/Content/Assets/VisualScript.cpp | 4 ++++ Source/Engine/Content/BinaryAsset.cpp | 2 ++ Source/Engine/Content/Content.cpp | 19 ++++++++++++++- Source/Engine/Content/JsonAsset.cpp | 6 +++++ .../Content/Loading/Tasks/LoadAssetTask.h | 2 ++ Source/Engine/Content/Storage/FlaxChunk.h | 7 +----- Source/Engine/Content/Storage/FlaxStorage.cpp | 23 ++++++++++++------- .../AssetsImportingManager.cpp | 2 ++ Source/Engine/Core/Log.cpp | 4 ++++ Source/Engine/Debug/DebugDraw.cpp | 2 ++ Source/Engine/Graphics/GPUBuffer.cpp | 5 ++++ Source/Engine/Graphics/GPUDevice.cpp | 1 + Source/Engine/Graphics/Graphics.cpp | 2 ++ .../Graphics/Materials/MaterialParams.cpp | 2 ++ .../Graphics/Materials/MaterialShader.cpp | 4 ++++ Source/Engine/Graphics/Models/Mesh.cpp | 3 +++ .../Graphics/Models/ModelInstanceEntry.cpp | 3 +++ Source/Engine/Graphics/RenderTask.cpp | 1 + Source/Engine/Graphics/Shaders/GPUShader.cpp | 7 +++++- .../Engine/Graphics/Textures/GPUTexture.cpp | 5 ++++ .../DirectX/DX11/GPUDeviceDX11.cpp | 6 +++++ .../DirectX/DX11/GPUSwapChainDX11.cpp | 4 ++++ .../DirectX/DX12/GPUDeviceDX12.cpp | 6 +++++ .../DirectX/DX12/GPUSwapChainDX12.cpp | 4 ++++ .../DirectX/DX12/UploadBufferDX12.cpp | 4 ++++ .../GraphicsDevice/Null/GPUDeviceNull.cpp | 5 ++++ .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 10 ++++++++ .../GraphicsDevice/Vulkan/GPUShaderVulkan.cpp | 3 +++ .../Vulkan/GPUSwapChainVulkan.cpp | 4 ++++ Source/Engine/Input/Input.cpp | 14 +++++++++++ Source/Engine/Level/Actor.cpp | 4 ++++ Source/Engine/Level/Level.cpp | 9 ++++++++ Source/Engine/Level/Scene/SceneRendering.cpp | 4 ++++ Source/Engine/Localization/CultureInfo.cpp | 5 ++++ Source/Engine/Localization/Localization.cpp | 4 ++++ .../Localization/LocalizedStringTable.cpp | 5 ++++ Source/Engine/Navigation/NavCrowd.cpp | 4 ++++ Source/Engine/Navigation/NavMeshData.cpp | 2 ++ Source/Engine/Navigation/NavMeshRuntime.cpp | 4 ++++ Source/Engine/Navigation/Navigation.cpp | 6 +++++ Source/Engine/Networking/NetworkManager.cpp | 8 +++++++ Source/Engine/Networking/NetworkPeer.cpp | 4 ++++ .../Engine/Networking/NetworkReplicator.cpp | 8 +++++++ Source/Engine/Networking/NetworkStream.cpp | 3 +++ Source/Engine/Particles/ParticleEffect.cpp | 5 ++++ Source/Engine/Particles/ParticleEmitter.cpp | 3 +++ .../Particles/ParticleEmitterFunction.cpp | 2 ++ Source/Engine/Particles/ParticleSystem.cpp | 3 +++ Source/Engine/Particles/Particles.cpp | 9 ++++++++ Source/Engine/Particles/ParticlesData.cpp | 2 ++ Source/Engine/Physics/Actors/Cloth.cpp | 8 +++++++ .../Engine/Physics/Actors/SplineRopeBody.cpp | 2 ++ Source/Engine/Physics/CollisionCooking.cpp | 2 ++ Source/Engine/Physics/CollisionData.cpp | 9 ++++++++ .../Physics/PhysX/PhysicsBackendPhysX.cpp | 6 +++++ .../PhysX/SimulationEventCallbackPhysX.cpp | 4 ++++ Source/Engine/Physics/Physics.cpp | 6 +++++ Source/Engine/Platform/Base/WindowBase.cpp | 20 ++++++++++++++++ Source/Engine/Platform/Unix/UnixThread.cpp | 6 ++++- Source/Engine/Profiler/ProfilingTools.cpp | 1 + Source/Engine/Render2D/Render2D.cpp | 2 ++ Source/Engine/Renderer/RenderList.cpp | 3 +++ Source/Engine/Renderer/Renderer.cpp | 3 +++ Source/Engine/Scripting/BinaryModule.cpp | 18 +++++++++++++++ Source/Engine/Scripting/ManagedCLR/MCore.cpp | 2 ++ .../Scripting/Plugins/PluginManager.cpp | 7 ++++++ Source/Engine/Scripting/Runtime/DotNet.cpp | 20 ++++++++++++++++ Source/Engine/Scripting/Scripting.cpp | 11 +++++++++ Source/Engine/Scripting/ScriptingObject.cpp | 4 ++++ Source/Engine/Streaming/Streaming.cpp | 5 ++++ Source/Engine/Threading/TaskGraph.cpp | 3 +++ Source/Engine/UI/TextRender.cpp | 5 ++++ Source/Engine/UI/UICanvas.cpp | 4 ++++ Source/Engine/UI/UIControl.cpp | 4 ++++ Source/Engine/Video/AV/VideoBackendAV.cpp | 4 ++++ Source/Engine/Video/MF/VideoBackendMF.cpp | 5 ++++ Source/Engine/Video/Video.cpp | 6 +++++ 101 files changed, 502 insertions(+), 17 deletions(-) diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index ea64bbae6..db8ded610 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -30,6 +30,7 @@ #include "Engine/Scripting/ManagedCLR/MAssembly.h" #include "Engine/Content/JsonAsset.h" #include "Engine/Content/AssetReference.h" +#include "Engine/Profiler/ProfilerMemory.h" #if PLATFORM_TOOLS_WINDOWS #include "Platform/Windows/WindowsPlatformTools.h" #include "Engine/Platform/Windows/WindowsPlatformSettings.h" @@ -380,6 +381,7 @@ bool GameCooker::IsCancelRequested() PlatformTools* GameCooker::GetTools(BuildPlatform platform) { + PROFILE_MEM(Editor); PlatformTools* result = nullptr; if (!Tools.TryGet(platform, result)) { @@ -471,6 +473,7 @@ bool GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, LOG(Error, "Build platform {0} is not supported.", ::ToString(platform)); return true; } + PROFILE_MEM(Editor); // Setup CancelFlag = 0; @@ -624,6 +627,7 @@ void GameCookerImpl::ReportProgress(const String& info, float totalProgress) void GameCookerImpl::OnCollectAssets(HashSet& assets) { + PROFILE_MEM(Editor); if (Internal_OnCollectAssets == nullptr) { auto c = GameCooker::GetStaticClass(); @@ -651,6 +655,7 @@ void GameCookerImpl::OnCollectAssets(HashSet& assets) bool GameCookerImpl::Build() { + PROFILE_MEM(Editor); CookingData& data = *Data; LOG(Info, "Starting Game Cooker..."); LOG(Info, "Platform: {0}, Configuration: {2}, Options: {1}", ::ToString(data.Platform), (int32)data.Options, ::ToString(data.Configuration)); @@ -778,6 +783,8 @@ int32 GameCookerImpl::ThreadFunction() bool GameCookerService::Init() { + PROFILE_MEM(Editor); + auto editorAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; editorAssembly->Unloading.Bind(OnEditorAssemblyUnloading); GameCooker::OnCollectAssets.Bind(OnCollectAssets); @@ -789,6 +796,7 @@ void GameCookerService::Update() { if (IsRunning) { + PROFILE_MEM(Editor); ScopeLock lock(ProgressLocker); if (ProgressMsg.HasChars()) diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp index e5f2da5e4..3549cb866 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp @@ -6,6 +6,8 @@ #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Stopwatch.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Engine/EngineService.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/BinaryModule.h" @@ -69,6 +71,7 @@ MTypeObject* CustomEditorsUtil::GetCustomEditor(MTypeObject* refType) bool CustomEditorsUtilService::Init() { + PROFILE_MEM(Editor); TRACK_ASSEMBLY(((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly); Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded); @@ -77,6 +80,8 @@ bool CustomEditorsUtilService::Init() void OnAssemblyLoaded(MAssembly* assembly) { + PROFILE_CPU_NAMED("CustomEditors.OnAssemblyLoaded"); + PROFILE_MEM(Editor); Stopwatch stopwatch; // Prepare FlaxEngine diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index 04c6eee71..99b7f1522 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -20,6 +20,7 @@ #include "Engine/Engine/Engine.h" #include "Engine/ShadowsOfMordor/Builder.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "FlaxEngine.Gen.h" #if PLATFORM_LINUX #include "Engine/Tools/TextureTool/TextureTool.h" @@ -47,6 +48,7 @@ void Editor::CloseSplashScreen() bool Editor::CheckProjectUpgrade() { + PROFILE_MEM(Editor); const auto versionFilePath = Globals::ProjectCacheFolder / TEXT("version"); // Load version cache file @@ -366,6 +368,8 @@ bool Editor::BackupProject() int32 Editor::LoadProduct() { + PROFILE_MEM(Editor); + // Flax Editor product Globals::ProductName = TEXT("Flax Editor"); Globals::CompanyName = TEXT("Flax"); @@ -626,6 +630,7 @@ int32 Editor::LoadProduct() Window* Editor::CreateMainWindow() { + PROFILE_MEM(Editor); Window* window = Managed->GetMainWindow(); #if PLATFORM_LINUX @@ -662,6 +667,7 @@ bool Editor::Init() return true; } PROFILE_CPU(); + PROFILE_MEM(Editor); // If during last lightmaps baking engine crashed we could try to restore the progress ShadowsOfMordor::Builder::Instance()->CheckIfRestoreState(); @@ -693,11 +699,13 @@ bool Editor::Init() void Editor::BeforeRun() { + PROFILE_MEM(Editor); Managed->BeforeRun(); } void Editor::BeforeExit() { + PROFILE_MEM(Editor); CloseSplashScreen(); Managed->Exit(); @@ -708,6 +716,8 @@ void Editor::BeforeExit() void EditorImpl::OnUpdate() { + PROFILE_MEM(Editor); + // Update c# editor Editor::Managed->Update(); diff --git a/Source/Editor/ProjectInfo.cpp b/Source/Editor/ProjectInfo.cpp index 6f2743fbf..bc9c1ecdb 100644 --- a/Source/Editor/ProjectInfo.cpp +++ b/Source/Editor/ProjectInfo.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Engine/Globals.h" #include "Engine/Core/Math/Quaternion.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/JsonWriters.h" #include "Engine/Serialization/JsonTools.h" #include @@ -327,6 +328,7 @@ ProjectInfo* ProjectInfo::Load(const String& path) } // Load + PROFILE_MEM(Editor); auto project = New(); if (project->LoadProject(path)) { diff --git a/Source/Editor/Scripting/CodeEditor.cpp b/Source/Editor/Scripting/CodeEditor.cpp index c78c9f48a..b372da189 100644 --- a/Source/Editor/Scripting/CodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditor.cpp @@ -13,6 +13,7 @@ #include "Engine/Engine/EngineService.h" #include "Engine/Platform/Thread.h" #include "Engine/Threading/IRunnable.h" +#include "Engine/Profiler/ProfilerMemory.h" void OnAsyncBegin(Thread* thread); void OnAsyncEnd(); @@ -232,6 +233,8 @@ void OnAsyncEnd() bool CodeEditingManagerService::Init() { + PROFILE_MEM(Editor); + // Try get editors #if USE_VISUAL_STUDIO_DTE VisualStudioEditor::FindEditors(&CodeEditors); diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 1bc67f79e..84df897e6 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -23,6 +23,7 @@ #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Script.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Level/Level.h" #include "FlaxEngine.Gen.h" @@ -413,6 +414,7 @@ void ScriptsBuilder::GetBinariesConfiguration(const Char*& target, const Char*& bool ScriptsBuilderImpl::compileGameScriptsAsyncInner() { + PROFILE_MEM(Editor); LOG(Info, "Starting scripts compilation..."); CallEvent(EventType::CompileStarted); @@ -519,6 +521,8 @@ void ScriptsBuilderImpl::onEditorAssemblyUnloading(MAssembly* assembly) bool ScriptsBuilderImpl::compileGameScriptsAsync() { + PROFILE_MEM(Editor); + // Start { ScopeLock scopeLock(_locker); @@ -562,6 +566,7 @@ bool ScriptsBuilderService::Init() // Check flag if (_isInited) return false; + PROFILE_MEM(Editor); _isInited = true; // Link for Editor assembly unload event to clear cached Internal_OnCompilationEnd to prevent errors diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 2562c9bf5..6bfdbb5f8 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -5,6 +5,7 @@ #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/MaterialInstance.h" #include "Engine/Content/Content.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Level/Level.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Actors/PointLight.h" @@ -253,6 +254,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor bool ViewportIconsRendererService::Init() { + PROFILE_MEM(Editor); QuadModel = Content::LoadAsyncInternal(TEXT("Engine/Models/Quad")); #define INIT(type, path) \ InstanceBuffers[static_cast(IconTypes::type)].Setup(1); \ diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index e70ca50a4..7e647e945 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -7,6 +7,7 @@ #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/TaskGraph.h" class BehaviorSystem : public TaskGraphSystem @@ -38,6 +39,7 @@ TaskGraphSystem* Behavior::System = nullptr; void BehaviorSystem::Job(int32 index) { PROFILE_CPU_NAMED("Behavior.Job"); + PROFILE_MEM(AI); Behaviors[index]->UpdateAsync(); } @@ -57,6 +59,7 @@ void BehaviorSystem::Execute(TaskGraph* graph) bool BehaviorService::Init() { + PROFILE_MEM(AI); Behavior::System = New(); Engine::UpdateGraph->AddSystem(Behavior::System); return false; diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp index c71bb0724..4d8e2eed9 100644 --- a/Source/Engine/AI/BehaviorKnowledge.cpp +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -4,6 +4,7 @@ #include "BehaviorTree.h" #include "BehaviorTreeNodes.h" #include "BehaviorKnowledgeSelector.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/ManagedCLR/MProperty.h" @@ -144,6 +145,7 @@ BehaviorKnowledge::~BehaviorKnowledge() void BehaviorKnowledge::InitMemory(BehaviorTree* tree) { + PROFILE_MEM(AI); if (Tree) FreeMemory(); if (!tree) diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index 64491b10e..ae58e983a 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -10,6 +10,7 @@ #include "Engine/Serialization/JsonSerializer.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Threading/Threading.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "FlaxEngine.Gen.h" #if USE_EDITOR #include "Engine/Level/Level.h" @@ -275,6 +276,7 @@ Asset::LoadResult BehaviorTree::load() if (surfaceChunk == nullptr) return LoadResult::MissingDataChunk; MemoryReadStream surfaceStream(surfaceChunk->Get(), surfaceChunk->Size()); + PROFILE_MEM(AI); if (Graph.Load(&surfaceStream, true)) { LOG(Warning, "Failed to load graph \'{0}\'", ToString()); diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp index 0a5a129e1..cc3be45b0 100644 --- a/Source/Engine/Animations/Animations.cpp +++ b/Source/Engine/Animations/Animations.cpp @@ -4,6 +4,7 @@ #include "AnimEvent.h" #include "Engine/Engine/Engine.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Level/Actors/AnimatedModel.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" @@ -69,6 +70,7 @@ AnimContinuousEvent::AnimContinuousEvent(const SpawnParams& params) bool AnimationsService::Init() { + PROFILE_MEM(Animations); Animations::System = New(); Engine::UpdateGraph->AddSystem(Animations::System); return false; @@ -83,6 +85,7 @@ void AnimationsService::Dispose() void AnimationsSystem::Job(int32 index) { PROFILE_CPU_NAMED("Animations.Job"); + PROFILE_MEM(Animations); auto animatedModel = AnimationManagerInstance.UpdateList[index]; if (CanUpdateModel(animatedModel)) { @@ -147,6 +150,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph) if (!Active) return; PROFILE_CPU_NAMED("Animations.PostExecute"); + PROFILE_MEM(Animations); // Update gameplay for (int32 index = 0; index < AnimationManagerInstance.UpdateList.Count(); index++) diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp index b1d51a89a..1a6c6fbee 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp @@ -7,6 +7,7 @@ #include "Engine/Content/Content.h" #include "Engine/Content/Deprecated.h" #include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Audio/AudioClip.h" #include "Engine/Graphics/PostProcessSettings.h" #if USE_EDITOR @@ -249,6 +250,7 @@ bool SceneAnimation::Save(const StringView& path) Asset::LoadResult SceneAnimation::load() { TrackStatesCount = 0; + PROFILE_MEM(AnimationsData); // Get the data chunk if (LoadChunk(0)) diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index eb3bf966e..18234c7ef 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -12,6 +12,7 @@ #include "Engine/Audio/AudioSource.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Renderer/RenderList.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Script.h" #include "Engine/Scripting/ManagedCLR/MException.h" @@ -151,6 +152,7 @@ void SceneAnimationPlayer::Tick(float dt) SceneAnimation* anim = Animation.Get(); if (!anim || !anim->IsLoaded()) return; + PROFILE_MEM(Animations); // Setup state if (_tracks.Count() != anim->TrackStatesCount) @@ -229,6 +231,7 @@ void SceneAnimationPlayer::MapTrack(const StringView& from, const Guid& to) SceneAnimation* anim = Animation.Get(); if (!anim || !anim->IsLoaded()) return; + PROFILE_MEM(Animations); for (int32 j = 0; j < anim->Tracks.Count(); j++) { const auto& track = anim->Tracks[j]; diff --git a/Source/Engine/Audio/Audio.cpp b/Source/Engine/Audio/Audio.cpp index e19efabf1..5574919c4 100644 --- a/Source/Engine/Audio/Audio.cpp +++ b/Source/Engine/Audio/Audio.cpp @@ -8,6 +8,7 @@ #include "Engine/Scripting/BinaryModule.h" #include "Engine/Level/Level.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Core/Log.h" @@ -151,6 +152,7 @@ void Audio::SetEnableHRTF(bool value) bool AudioService::Init() { PROFILE_CPU_NAMED("Audio.Init"); + PROFILE_MEM(Audio); const auto settings = AudioSettings::Get(); const bool mute = CommandLine::Options.Mute.IsTrue() || settings->DisableAudio; @@ -211,6 +213,7 @@ bool AudioService::Init() void AudioService::Update() { PROFILE_CPU_NAMED("Audio.Update"); + PROFILE_MEM(Audio); // Update the master volume float masterVolume = MasterVolume; diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 8835cddc7..2ac9d218c 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -10,6 +10,7 @@ #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Streaming/StreamingGroup.h" #include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Tools/AudioTool/OggVorbisDecoder.h" #include "Engine/Tools/AudioTool/AudioTool.h" #include "Engine/Threading/Threading.h" @@ -318,6 +319,7 @@ bool AudioClip::init(AssetInitData& initData) Asset::LoadResult AudioClip::load() { + PROFILE_MEM(Audio); #if !COMPILE_WITH_OGG_VORBIS if (AudioHeader.Format == AudioFormat::Vorbis) { diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index dfdca18f6..86e828028 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -9,6 +9,7 @@ #include "Engine/Tools/AudioTool/AudioTool.h" #include "Engine/Engine/Units.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Audio/Audio.h" #include "Engine/Audio/AudioListener.h" #include "Engine/Audio/AudioSource.h" @@ -321,6 +322,8 @@ void AudioBackendOAL::Listener_ReinitializeAll() uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) { + PROFILE_MEM(Audio); + uint32 sourceID = 0; ALC::Source::Rebuild(sourceID, position, orientation, volume, pitch, pan, loop, spatial, attenuation, minDistance, doppler); @@ -516,6 +519,7 @@ void AudioBackendOAL::Buffer_Delete(uint32 bufferID) void AudioBackendOAL::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) { PROFILE_CPU(); + PROFILE_MEM(Audio); // Pick the format for the audio data (it might not be supported natively) ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth); @@ -625,6 +629,8 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features() void AudioBackendOAL::Base_OnActiveDeviceChanged() { + PROFILE_MEM(Audio); + // Cleanup Array states; states.EnsureCapacity(Audio::Sources.Count()); diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 0d5391482..3a6ed648e 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -526,6 +526,7 @@ ContentLoadTask* Asset::createLoadingTask() void Asset::startLoading() { + PROFILE_MEM(ContentAssets); ASSERT(!IsLoaded()); ASSERT(Platform::AtomicRead(&_loadingTask) == 0); auto loadingTask = createLoadingTask(); diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 54c4d898e..8558e601d 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -9,6 +9,7 @@ #include "Engine/Animations/Animations.h" #include "Engine/Animations/SceneAnimations/SceneAnimation.h" #include "Engine/Scripting/Scripting.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/Threading.h" #include "Engine/Serialization/MemoryReadStream.h" #if USE_EDITOR @@ -598,6 +599,7 @@ void Animation::OnScriptingDispose() Asset::LoadResult Animation::load() { + PROFILE_MEM(AnimationsData); ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker); // Get stream with animations data diff --git a/Source/Engine/Content/Assets/AnimationGraph.cpp b/Source/Engine/Content/Assets/AnimationGraph.cpp index 3e4a96ea2..acab48b2f 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.cpp +++ b/Source/Engine/Content/Assets/AnimationGraph.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Types/DataContainer.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Animations/Animations.h" #include "Engine/Threading/Threading.h" @@ -25,6 +26,7 @@ AnimationGraph::AnimationGraph(const SpawnParams& params, const AssetInfo* info) Asset::LoadResult AnimationGraph::load() { + PROFILE_MEM(AnimationsData); ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker); // Get stream with graph data @@ -83,6 +85,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b Log::ArgumentNullException(); return true; } + PROFILE_MEM(AnimationsData); ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker); // Create Graph data diff --git a/Source/Engine/Content/Assets/AnimationGraphFunction.cpp b/Source/Engine/Content/Assets/AnimationGraphFunction.cpp index 76c84977a..3e8ce62e8 100644 --- a/Source/Engine/Content/Assets/AnimationGraphFunction.cpp +++ b/Source/Engine/Content/Assets/AnimationGraphFunction.cpp @@ -8,6 +8,7 @@ #include "Engine/Serialization/MemoryWriteStream.h" #endif #include "Engine/Animations/Animations.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Threading/Threading.h" @@ -20,6 +21,7 @@ AnimationGraphFunction::AnimationGraphFunction(const SpawnParams& params, const Asset::LoadResult AnimationGraphFunction::load() { + PROFILE_MEM(AnimationsData); ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker); // Get graph data from chunk diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 85e63fc18..ce457c862 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -165,9 +165,13 @@ Asset::LoadResult Material::load() MaterialGenerator generator; generator.Error.Bind(&OnGeneratorError); if (_shaderHeader.Material.GraphVersion != MATERIAL_GRAPH_VERSION) + { LOG(Info, "Converting material \'{0}\', from version {1} to {2}...", name, _shaderHeader.Material.GraphVersion, MATERIAL_GRAPH_VERSION); + } else + { LOG(Info, "Updating material \'{0}\'...", name); + } // Load or create material surface MaterialLayer* layer; diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 1f5768e6c..10c471f8c 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -18,6 +18,7 @@ #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Threading/Threading.h" #include "Engine/Tools/ModelTool/ModelTool.h" @@ -304,6 +305,7 @@ bool Model::Init(const Span& meshesCountPerLod) Log::ArgumentOutOfRangeException(); return true; } + PROFILE_MEM(GraphicsMeshes); // Dispose previous data and disable streaming (will start data uploading tasks manually) StopStreaming(); @@ -343,6 +345,7 @@ bool Model::Init(const Span& meshesCountPerLod) bool Model::LoadHeader(ReadStream& stream, byte& headerVersion) { + PROFILE_MEM(GraphicsMeshes); if (ModelBase::LoadHeader(stream, headerVersion)) return true; @@ -509,6 +512,7 @@ bool Model::Save(bool withMeshDataFromGpu, Function& getChunk void Model::SetupMaterialSlots(int32 slotsCount) { + PROFILE_MEM(GraphicsMeshes); ModelBase::SetupMaterialSlots(slotsCount); // Adjust meshes indices for slots @@ -584,6 +588,8 @@ int32 Model::GetAllocatedResidency() const Asset::LoadResult Model::load() { + PROFILE_MEM(GraphicsMeshes); + // Get header chunk auto chunk0 = GetChunk(0); if (chunk0 == nullptr || chunk0->IsMissing()) diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 243e429a5..5f2a89328 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Math/Transform.h" #include "Engine/Content/WeakAssetReference.h" #include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Graphics/Config.h" #include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Graphics/Models/MeshDeformation.h" @@ -51,6 +52,7 @@ public: AssetReference model = _model.Get(); if (model == nullptr) return true; + PROFILE_MEM(GraphicsMeshes); // Get data BytesContainer data; @@ -334,6 +336,8 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion) bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) { + PROFILE_MEM(GraphicsMeshes); + // Load descriptor static_assert(MODEL_MESH_VERSION == 2, "Update code"); uint32 vertices, triangles; diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 5271df723..7c51f4332 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -18,6 +18,7 @@ #include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Renderer/DrawCall.h" #if USE_EDITOR #include "Engine/Graphics/Models/ModelData.h" @@ -458,6 +459,7 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) Log::ArgumentOutOfRangeException(); return true; } + PROFILE_MEM(GraphicsMeshes); // Dispose previous data and disable streaming (will start data uploading tasks manually) StopStreaming(); @@ -501,6 +503,7 @@ void BlendShape::LoadHeader(ReadStream& stream, byte headerVersion) void BlendShape::Load(ReadStream& stream, byte meshVersion) { + PROFILE_MEM(GraphicsMeshes); UseNormals = stream.ReadBool(); stream.ReadUint32(&MinVertexIndex); stream.ReadUint32(&MaxVertexIndex); @@ -531,6 +534,7 @@ void BlendShape::Save(WriteStream& stream) const bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) { + PROFILE_MEM(GraphicsMeshes); if (ModelBase::LoadMesh(stream, meshVersion, mesh, dataIfReadOnly)) return true; static_assert(MODEL_MESH_VERSION == 2, "Update code"); @@ -560,6 +564,7 @@ bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion) { + PROFILE_MEM(GraphicsMeshes); if (ModelBase::LoadHeader(stream, headerVersion)) return true; static_assert(MODEL_HEADER_VERSION == 2, "Update code"); @@ -861,6 +866,7 @@ uint64 SkinnedModel::GetMemoryUsage() const void SkinnedModel::SetupMaterialSlots(int32 slotsCount) { + PROFILE_MEM(GraphicsMeshes); ModelBase::SetupMaterialSlots(slotsCount); // Adjust meshes indices for slots @@ -954,6 +960,7 @@ Asset::LoadResult SkinnedModel::load() if (chunk0 == nullptr || chunk0->IsMissing()) return LoadResult::MissingDataChunk; MemoryReadStream headerStream(chunk0->Get(), chunk0->Size()); + PROFILE_MEM(GraphicsMeshes); // Load asset data (anything but mesh contents that use streaming) byte headerVersion; diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 66d95ca87..a6d83919c 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -1900,9 +1900,13 @@ bool VisualScriptingBinaryModule::InvokeMethod(void* method, const Variant& inst if (!instanceObject || instanceObject->GetTypeHandle() != vsMethod->Script->GetScriptingType()) { if (!instanceObject) + { LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(vsMethod->Script->GetScriptTypeName()), String(vsMethod->Name), vsMethod->ParamNames.Count()); + } else + { LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid object instance of type '{3}'", String(vsMethod->Script->GetScriptTypeName()), String(vsMethod->Name), vsMethod->ParamNames.Count(), String(instanceObject->GetType().Fullname)); + } return true; } } diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 76980f3d6..9e7e51113 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -10,6 +10,7 @@ #include "Engine/Serialization/JsonTools.h" #include "Engine/Debug/Exceptions/JsonParseException.h" #include "Engine/Threading/ThreadPoolTask.h" +#include "Engine/Profiler/ProfilerMemory.h" #if USE_EDITOR #include "Engine/Platform/FileSystem.h" #include "Engine/Threading/Threading.h" @@ -527,6 +528,7 @@ protected: auto storage = ref->Storage; auto factory = (BinaryAssetFactoryBase*)Content::GetAssetFactory(ref->GetTypeName()); ASSERT(factory); + PROFILE_MEM(ContentAssets); // Here we should open storage and extract AssetInitData // This would also allow to convert/upgrade data diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index a6971e875..915f48140 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -28,6 +28,7 @@ #include "Engine/Engine/Globals.h" #include "Engine/Level/Types.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/Scripting.h" #if USE_EDITOR @@ -117,6 +118,8 @@ ContentService ContentServiceInstance; bool ContentService::Init() { + PROFILE_MEM(Content); + // Load assets registry Cache.Init(); @@ -159,6 +162,7 @@ void ContentService::Update() void ContentService::LateUpdate() { PROFILE_CPU(); + PROFILE_MEM(Content); // Check if need to perform an update of unloading assets const TimeSpan timeNow = Time::Update.UnscaledTime; @@ -324,6 +328,7 @@ String LoadingThread::ToString() const int32 LoadingThread::Run() { + PROFILE_MEM(Content); #if USE_EDITOR && PLATFORM_WINDOWS // Initialize COM // TODO: maybe add sth to Thread::Create to indicate that thread will use COM stuff @@ -416,6 +421,7 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info) if (Cache.FindAsset(id, info)) return true; PROFILE_CPU(); + PROFILE_MEM(Content); // Locking injects some stalls but we need to make it safe (only one thread can pass though it at once) ScopeLock lock(WorkspaceDiscoveryLocker); @@ -465,6 +471,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info) if (!FileSystem::FileExists(path)) return false; PROFILE_CPU(); + PROFILE_MEM(Content); const auto extension = FileSystem::GetExtension(path).ToLower(); @@ -593,6 +600,7 @@ Asset* Content::LoadAsyncInternal(const StringView& internalPath, const MClass* Asset* Content::LoadAsyncInternal(const StringView& internalPath, const ScriptingTypeHandle& type) { + PROFILE_MEM(Content); #if USE_EDITOR const String path = Globals::EngineContentFolder / internalPath + ASSET_FILES_EXTENSION_WITH_DOT; if (!FileSystem::FileExists(path)) @@ -635,6 +643,8 @@ Asset* Content::LoadAsync(const StringView& path, const MClass* type) Asset* Content::LoadAsync(const StringView& path, const ScriptingTypeHandle& type) { + PROFILE_MEM(Content); + // Ensure path is in a valid format String pathNorm(path); ContentStorageManager::FormatPath(pathNorm); @@ -687,7 +697,6 @@ Asset* Content::GetAsset(const StringView& outputPath) { if (outputPath.IsEmpty()) return nullptr; - ScopeLock lock(AssetsLocker); for (auto i = Assets.Begin(); i.IsNotEnd(); ++i) { @@ -1023,6 +1032,7 @@ Asset* Content::CreateVirtualAsset(const MClass* type) Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type) { PROFILE_CPU(); + PROFILE_MEM(Content); auto& assetType = type.GetType(); // Init mock asset info @@ -1045,7 +1055,9 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type) } // Create asset object + PROFILE_MEM_BEGIN(ContentAssets); auto asset = factory->NewVirtual(info); + PROFILE_MEM_END(); if (asset == nullptr) { LOG(Error, "Cannot create virtual asset object."); @@ -1054,7 +1066,9 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type) asset->RegisterObject(); // Call initializer function + PROFILE_MEM_BEGIN(ContentAssets); asset->InitAsVirtual(); + PROFILE_MEM_END(); // Register asset AssetsLocker.Lock(); @@ -1209,6 +1223,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) { if (!id.IsValid()) return nullptr; + PROFILE_MEM(Content); // Check if asset has been already loaded Asset* result = nullptr; @@ -1277,7 +1292,9 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) } // Create asset object + PROFILE_MEM_BEGIN(ContentAssets); result = factory->New(assetInfo); + PROFILE_MEM_END(); if (result == nullptr) { LOG(Error, "Cannot create asset object. Info: {0}", assetInfo.ToString()); diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 60f05d38e..04487eb65 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -20,6 +20,7 @@ #include "Engine/Core/Cache.h" #include "Engine/Debug/Exceptions/JsonParseException.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MField.h" @@ -39,6 +40,7 @@ String JsonAssetBase::GetData() const if (Data == nullptr) return String::Empty; PROFILE_CPU_NAMED("JsonAsset.GetData"); + PROFILE_MEM(ContentAssets); rapidjson_flax::StringBuffer buffer; OnGetData(buffer); return String((const char*)buffer.GetString(), (int32)buffer.GetSize()); @@ -49,6 +51,7 @@ void JsonAssetBase::SetData(const StringView& value) if (!IsLoaded()) return; PROFILE_CPU_NAMED("JsonAsset.SetData"); + PROFILE_MEM(ContentAssets); const StringAnsi dataJson(value); ScopeLock lock(Locker); const StringView dataTypeName = DataTypeName; @@ -60,6 +63,7 @@ void JsonAssetBase::SetData(const StringView& value) bool JsonAssetBase::Init(const StringView& dataTypeName, const StringAnsiView& dataJson) { + PROFILE_MEM(ContentAssets); unload(true); DataTypeName = dataTypeName; DataEngineBuild = FLAXENGINE_VERSION_BUILD; @@ -239,6 +243,7 @@ Asset::LoadResult JsonAssetBase::loadAsset() { if (IsVirtual() || _isVirtualDocument) return LoadResult::Ok; + PROFILE_MEM(ContentAssets); // Load data (raw json file in editor, cooked asset in build game) #if USE_EDITOR @@ -453,6 +458,7 @@ bool JsonAsset::CreateInstance() ScopeLock lock(Locker); if (Instance) return false; + PROFILE_MEM(ContentAssets); // Try to scripting type for this data const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length()); diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index 18d870616..4eae2829b 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -8,6 +8,7 @@ #include "Engine/Content/WeakAssetReference.h" #include "Engine/Core/Log.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" /// /// Asset loading task object. @@ -44,6 +45,7 @@ protected: Result run() override { PROFILE_CPU(); + PROFILE_MEM(ContentAssets); // Keep valid ref to the asset AssetReference<::Asset> ref = Asset.Get(); diff --git a/Source/Engine/Content/Storage/FlaxChunk.h b/Source/Engine/Content/Storage/FlaxChunk.h index 5121a0f0f..6e9887574 100644 --- a/Source/Engine/Content/Storage/FlaxChunk.h +++ b/Source/Engine/Content/Storage/FlaxChunk.h @@ -182,10 +182,5 @@ public: /// Clones this chunk data (doesn't copy location in file). /// /// The cloned chunk. - FlaxChunk* Clone() const - { - auto chunk = New(); - chunk->Data.Copy(Data); - return chunk; - } + FlaxChunk* Clone() const; }; diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 9e36ad632..17daba278 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Platform/File.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Content/Asset.h" #include "Engine/Content/Content.h" @@ -63,6 +64,14 @@ void FlaxChunk::RegisterUsage() LastAccessTime = Platform::GetTimeSeconds(); } +FlaxChunk* FlaxChunk::Clone() const +{ + PROFILE_MEM(ContentFiles); + auto chunk = New(); + chunk->Data.Copy(Data); + return chunk; +} + const int32 FlaxStorage::MagicCode = 1180124739; FlaxStorage::LockData FlaxStorage::LockData::Invalid(nullptr); @@ -281,19 +290,12 @@ uint32 FlaxStorage::GetMemoryUsage() const bool FlaxStorage::Load() { - // Check if was already loaded if (IsLoaded()) - { return false; - } - - // Prevent loading by more than one thread + PROFILE_MEM(ContentFiles); ScopeLock lock(_loadLocker); if (IsLoaded()) - { - // Other thread loaded it return false; - } ASSERT(GetEntriesCount() == 0); // Open file @@ -693,6 +695,7 @@ bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data) bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk) { + PROFILE_MEM(ContentFiles); ASSERT(IsLoaded()); ASSERT(chunk != nullptr && _chunks.Contains(chunk)); @@ -866,6 +869,7 @@ FlaxChunk* FlaxStorage::AllocateChunk() { if (AllowDataModifications()) { + PROFILE_MEM(ContentFiles); auto chunk = New(); _chunks.Add(chunk); return chunk; @@ -1125,6 +1129,7 @@ bool FlaxStorage::Save(const AssetInitData& data, bool silentMode) bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) { + PROFILE_MEM(ContentFiles); ASSERT(IsLoaded()); auto lock = Lock(); @@ -1396,6 +1401,8 @@ FileReadStream* FlaxStorage::OpenFile() auto& stream = _file.Get(); if (stream == nullptr) { + PROFILE_MEM(ContentFiles); + // Open file auto file = File::Open(_path, FileMode::OpenExisting, FileAccess::Read, FileShare::Read); if (file == nullptr) diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index b3cf8e419..9cad287dc 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -13,6 +13,7 @@ #include "Engine/Engine/EngineService.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/Platform.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Engine/Globals.h" #include "ImportTexture.h" #include "ImportModel.h" @@ -151,6 +152,7 @@ bool CreateAssetContext::AllocateChunk(int32 index) } // Create new chunk + PROFILE_MEM(ContentFiles); Data.Header.Chunks[index] = New(); return false; } diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index 215f490be..85cc3cc02 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -8,6 +8,7 @@ #include "Engine/Engine/Globals.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/CriticalSection.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Debug/Exceptions/Exceptions.h" #if USE_EDITOR @@ -42,6 +43,7 @@ bool Log::Logger::Init() // Skip if disabled if (!IsLogEnabled()) return false; + PROFILE_MEM(Engine); // Create logs directory (if is missing) #if USE_EDITOR @@ -119,6 +121,7 @@ void Log::Logger::Write(const StringView& msg) const auto length = msg.Length(); if (length <= 0) return; + PROFILE_MEM(Engine); LogLocker.Lock(); if (IsDuringLog) @@ -258,6 +261,7 @@ void Log::Logger::Write(LogType type, const StringView& msg) { if (msg.Length() <= 0) return; + PROFILE_MEM(Engine); const bool isError = IsError(type); // Create message for the log file diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index a026267cc..df2e41803 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -515,6 +515,7 @@ DebugDrawService DebugDrawServiceInstance; bool DebugDrawService::Init() { + PROFILE_MEM(Graphics); Context = &GlobalContext; // Init wireframe sphere cache @@ -633,6 +634,7 @@ void DebugDrawService::Update() } PROFILE_CPU(); + PROFILE_MEM(Graphics); // Update lists float deltaTime = Time::Update.DeltaTime.GetTotalSeconds(); diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index 0e71ea8b3..db7845227 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -15,6 +15,7 @@ #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Enums.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/Threading.h" @@ -188,6 +189,8 @@ bool GPUBuffer::IsDynamic() const bool GPUBuffer::Init(const GPUBufferDescription& desc) { + PROFILE_MEM(GraphicsBuffers); + // Validate description #if !BUILD_RELEASE #define GET_NAME() GetName() @@ -241,6 +244,7 @@ bool GPUBuffer::Init(const GPUBufferDescription& desc) LOG(Warning, "Cannot initialize buffer. Description: {0}", desc.ToString()); return true; } + PROFILE_MEM_INC(GraphicsBuffers, GetMemoryUsage()); return false; } @@ -476,6 +480,7 @@ GPUResourceType GPUBuffer::GetResourceType() const void GPUBuffer::OnReleaseGPU() { + PROFILE_MEM_DEC(GraphicsBuffers, GetMemoryUsage()); _desc.Clear(); _isLocked = false; } diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index e4bbc170a..1ea008913 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -650,6 +650,7 @@ GPUTasksExecutor* GPUDevice::CreateTasksExecutor() void GPUDevice::Draw() { + PROFILE_MEM(Graphics); DrawBegin(); auto context = GetMainContext(); diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 7ffba3957..43bd1a76d 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -9,6 +9,7 @@ #include "Engine/Engine/CommandLine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerGPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Render2D/Font.h" bool Graphics::UseVSync = false; @@ -97,6 +98,7 @@ void Graphics::DisposeDevice() bool GraphicsService::Init() { ASSERT(GPUDevice::Instance == nullptr); + PROFILE_MEM(Graphics); // Create and initialize graphics device Log::Logger::WriteFloor(); diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index e31697f77..55726c20c 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -15,6 +15,7 @@ #include "Engine/Renderer/GlobalSignDistanceFieldPass.h" #include "Engine/Scripting/Enums.h" #include "Engine/Streaming/Streaming.h" +#include "Engine/Profiler/ProfilerMemory.h" bool MaterialInfo8::operator==(const MaterialInfo8& other) const { @@ -638,6 +639,7 @@ void MaterialParams::Dispose() bool MaterialParams::Load(ReadStream* stream) { + PROFILE_MEM(GraphicsMaterials); bool result = false; // Release diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index 1b0b6937b..c24631d56 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -11,6 +11,7 @@ #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Engine/Time.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "DecalMaterialShader.h" #include "PostFxMaterialShader.h" #include "ForwardMaterialShader.h" @@ -136,6 +137,7 @@ MaterialShader::~MaterialShader() MaterialShader* MaterialShader::Create(const StringView& name, MemoryReadStream& shaderCacheStream, const MaterialInfo& info) { + PROFILE_MEM(GraphicsMaterials); MaterialShader* material; switch (info.Domain) { @@ -199,6 +201,7 @@ protected: MaterialShader* MaterialShader::CreateDummy(MemoryReadStream& shaderCacheStream, const MaterialInfo& info) { + PROFILE_MEM(GraphicsMaterials); MaterialShader* material = New(); if (material->Load(shaderCacheStream, info)) { @@ -225,6 +228,7 @@ bool MaterialShader::IsReady() const bool MaterialShader::Load(MemoryReadStream& shaderCacheStream, const MaterialInfo& info) { + PROFILE_MEM(GraphicsMaterials); ASSERT(!_isLoaded); // Cache material info diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 36dc18af4..32df730e8 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -14,6 +14,7 @@ #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Threading/Threading.h" +#include "Engine/Profiler/ProfilerMemory.h" #if USE_EDITOR #include "Engine/Renderer/GBufferPass.h" #endif @@ -48,6 +49,7 @@ namespace { bool UpdateMesh(MeshBase* mesh, uint32 vertexCount, uint32 triangleCount, PixelFormat indexFormat, const Float3* vertices, const void* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { + PROFILE_MEM(GraphicsMeshes); auto model = mesh->GetModelBase(); CHECK_RETURN(model && model->IsVirtual(), true); CHECK_RETURN(triangles && vertices, true); @@ -172,6 +174,7 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0Element bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices) { + PROFILE_MEM(GraphicsMeshes); Release(); // Setup GPU resources diff --git a/Source/Engine/Graphics/Models/ModelInstanceEntry.cpp b/Source/Engine/Graphics/Models/ModelInstanceEntry.cpp index ace033d6d..1c9440b39 100644 --- a/Source/Engine/Graphics/Models/ModelInstanceEntry.cpp +++ b/Source/Engine/Graphics/Models/ModelInstanceEntry.cpp @@ -4,6 +4,7 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/SkinnedModel.h" +#include "Engine/Profiler/ProfilerMemory.h" bool ModelInstanceEntries::HasContentLoaded() const { @@ -41,6 +42,7 @@ void ModelInstanceEntries::Serialize(SerializeStream& stream, const void* otherO void ModelInstanceEntries::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { + PROFILE_MEM(Graphics); const DeserializeStream& entries = stream["Entries"]; ASSERT(entries.IsArray()); Resize(entries.Size()); @@ -85,6 +87,7 @@ void ModelInstanceEntries::Setup(const SkinnedModel* model) void ModelInstanceEntries::Setup(int32 slotsCount) { + PROFILE_MEM(Graphics); Clear(); Resize(slotsCount); } diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index df45d981c..ecdcd572c 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -417,6 +417,7 @@ void SceneRenderTask::OnEnd(GPUContext* context) bool SceneRenderTask::Resize(int32 width, int32 height) { + PROFILE_MEM(Graphics); if (Output && Output->Resize(width, height)) return true; if (Buffers && Buffers->Init((int32)((float)width * RenderingPercentage), (int32)((float)height * RenderingPercentage))) diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index 2d14c1796..694b13f40 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -8,6 +8,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Profiler/ProfilerMemory.h" static FORCE_INLINE uint32 HashPermutation(const StringAnsiView& name, int32 permutationIndex) { @@ -33,6 +34,7 @@ GPUShader::GPUShader() bool GPUShader::Create(MemoryReadStream& stream) { ReleaseGPU(); + _memoryUsage = sizeof(GPUShader); // Version int32 version; @@ -111,6 +113,7 @@ bool GPUShader::Create(MemoryReadStream& stream) const uint32 hash = HashPermutation(shader->GetName(), permutationIndex); ASSERT_LOW_LAYER(!_shaders.ContainsKey(hash)); _shaders.Add(hash, shader); + _memoryUsage += sizeof(GPUShaderProgram) + bytecodeSize; } } @@ -142,11 +145,12 @@ bool GPUShader::Create(MemoryReadStream& stream) return true; } _constantBuffers[slotIndex] = cb; + _memoryUsage += sizeof(GPUConstantBuffer); } // Don't read additional data - _memoryUsage = 1; + PROFILE_MEM_INC(GraphicsShaders, _memoryUsage); return false; } @@ -208,6 +212,7 @@ GPUResourceType GPUShader::GetResourceType() const void GPUShader::OnReleaseGPU() { + PROFILE_MEM_DEC(GraphicsShaders, _memoryUsage); for (GPUConstantBuffer*& cb : _constantBuffers) { if (cb) diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 127cf2f5b..6d138a40c 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -16,6 +16,7 @@ #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Enums.h" namespace @@ -353,6 +354,8 @@ int32 GPUTexture::ComputeRowPitch(int32 mipLevel, int32 rowAlign) const bool GPUTexture::Init(const GPUTextureDescription& desc) { + PROFILE_MEM(GraphicsTextures); + // Validate description const auto device = GPUDevice::Instance; if (desc.Usage == GPUResourceUsage::Dynamic) @@ -500,6 +503,7 @@ bool GPUTexture::Init(const GPUTextureDescription& desc) LOG(Warning, "Cannot initialize texture. Description: {0}", desc.ToString()); return true; } + PROFILE_MEM_INC(GraphicsTextures, GetMemoryUsage()); // Render targets and depth buffers doesn't support normal textures streaming and are considered to be always resident if (IsRegularTexture() == false) @@ -589,6 +593,7 @@ GPUResourceType GPUTexture::GetResourceType() const void GPUTexture::OnReleaseGPU() { + PROFILE_MEM_DEC(GraphicsTextures, GetMemoryUsage()); _desc.Clear(); _residentMipLevels = 0; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index ad39b777d..c578fd295 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -19,6 +19,7 @@ #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Profiler/ProfilerMemory.h" #if !USE_EDITOR && PLATFORM_WINDOWS #include "Engine/Core/Config/PlatformSettings.h" @@ -810,16 +811,19 @@ void GPUDeviceDX11::DrawEnd() GPUTexture* GPUDeviceDX11::CreateTexture(const StringView& name) { + PROFILE_MEM(GraphicsTextures); return New(this, name); } GPUShader* GPUDeviceDX11::CreateShader(const StringView& name) { + PROFILE_MEM(GraphicsShaders); return New(this, name); } GPUPipelineState* GPUDeviceDX11::CreatePipelineState() { + PROFILE_MEM(GraphicsCommands); return New(this); } @@ -830,6 +834,7 @@ GPUTimerQuery* GPUDeviceDX11::CreateTimerQuery() GPUBuffer* GPUDeviceDX11::CreateBuffer(const StringView& name) { + PROFILE_MEM(GraphicsBuffers); return New(this, name); } @@ -850,6 +855,7 @@ GPUSwapChain* GPUDeviceDX11::CreateSwapChain(Window* window) GPUConstantBuffer* GPUDeviceDX11::CreateConstantBuffer(uint32 size, const StringView& name) { + PROFILE_MEM(GraphicsShaders); ID3D11Buffer* buffer = nullptr; uint32 memorySize = 0; if (size) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp index ba7226250..7a106d377 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp @@ -6,6 +6,7 @@ #include "Engine/Platform/Window.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "GPUContextDX11.h" GPUSwapChainDX11::GPUSwapChainDX11(GPUDeviceDX11* device, Window* window) @@ -60,9 +61,11 @@ void GPUSwapChainDX11::OnReleaseGPU() #endif // Release data + PROFILE_MEM_DEC(Graphics, _memoryUsage); releaseBackBuffer(); DX_SAFE_RELEASE_CHECK(_swapChain, 0); _width = _height = 0; + _memoryUsage = 0; } ID3D11Resource* GPUSwapChainDX11::GetResource() @@ -262,6 +265,7 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height) _width = width; _height = height; _memoryUsage = RenderTools::CalculateTextureMemoryUsage(_format, _width, _height, 1) * swapChainDesc.BufferCount; + PROFILE_MEM_INC(Graphics, _memoryUsage); getBackBuffer(); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 036454589..40e081175 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -18,6 +18,7 @@ #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Core/Log.h" #include "Engine/Core/Config/PlatformSettings.h" #include "UploadBufferDX12.h" @@ -833,16 +834,19 @@ void GPUDeviceDX12::WaitForGPU() GPUTexture* GPUDeviceDX12::CreateTexture(const StringView& name) { + PROFILE_MEM(GraphicsTextures); return New(this, name); } GPUShader* GPUDeviceDX12::CreateShader(const StringView& name) { + PROFILE_MEM(GraphicsShaders); return New(this, name); } GPUPipelineState* GPUDeviceDX12::CreatePipelineState() { + PROFILE_MEM(GraphicsCommands); return New(this); } @@ -853,6 +857,7 @@ GPUTimerQuery* GPUDeviceDX12::CreateTimerQuery() GPUBuffer* GPUDeviceDX12::CreateBuffer(const StringView& name) { + PROFILE_MEM(GraphicsBuffers); return New(this, name); } @@ -873,6 +878,7 @@ GPUSwapChain* GPUDeviceDX12::CreateSwapChain(Window* window) GPUConstantBuffer* GPUDeviceDX12::CreateConstantBuffer(uint32 size, const StringView& name) { + PROFILE_MEM(GraphicsShaders); return New(this, size, name); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp index bfbf662b7..fa6dfa881 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp @@ -6,6 +6,7 @@ #include "GPUContextDX12.h" #include "../IncludeDirectXHeaders.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Profiler/ProfilerMemory.h" void BackBufferDX12::Setup(GPUSwapChainDX12* window, ID3D12Resource* backbuffer) { @@ -71,6 +72,7 @@ void GPUSwapChainDX12::OnReleaseGPU() #endif // Release data + PROFILE_MEM_DEC(Graphics, _memoryUsage); releaseBackBuffer(); _backBuffers.Resize(0); if (_swapChain) @@ -79,6 +81,7 @@ void GPUSwapChainDX12::OnReleaseGPU() _swapChain = nullptr; } _width = _height = 0; + _memoryUsage = 0; } void GPUSwapChainDX12::releaseBackBuffer() @@ -244,6 +247,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height) _width = width; _height = height; _memoryUsage = RenderTools::CalculateTextureMemoryUsage(_format, _width, _height, 1) * swapChainDesc.BufferCount; + PROFILE_MEM_INC(Graphics, _memoryUsage); getBackBuffer(); #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp index 1e6613744..8fdfd5ac3 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp @@ -6,6 +6,7 @@ #include "GPUTextureDX12.h" #include "GPUContextDX12.h" #include "../RenderToolsDX.h" +#include "Engine/Profiler/ProfilerMemory.h" UploadBufferDX12::UploadBufferDX12(GPUDeviceDX12* device) : _device(device) @@ -235,6 +236,7 @@ UploadBufferPageDX12::UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size) initResource(resource, D3D12_RESOURCE_STATE_GENERIC_READ, 1); DX_SET_DEBUG_NAME(_resource, GPUResourceDX12::GetName()); _memoryUsage = size; + PROFILE_MEM_INC(GraphicsCommands, _memoryUsage); GPUAddress = _resource->GetGPUVirtualAddress(); // Map buffer @@ -243,6 +245,8 @@ UploadBufferPageDX12::UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size) void UploadBufferPageDX12::OnReleaseGPU() { + PROFILE_MEM_DEC(GraphicsCommands, _memoryUsage); + // Unmap if (_resource && CPUAddress) { diff --git a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp index 41ec9f76a..a1582102f 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp +++ b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp @@ -14,6 +14,7 @@ #include "GPUVertexLayoutNull.h" #include "GPUSwapChainNull.h" #include "Engine/Core/Log.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Graphics/Async/GPUTasksManager.h" GPUDeviceNull::GPUDeviceNull() @@ -145,16 +146,19 @@ void GPUDeviceNull::WaitForGPU() GPUTexture* GPUDeviceNull::CreateTexture(const StringView& name) { + PROFILE_MEM(GraphicsTextures); return New(); } GPUShader* GPUDeviceNull::CreateShader(const StringView& name) { + PROFILE_MEM(GraphicsShaders); return New(); } GPUPipelineState* GPUDeviceNull::CreatePipelineState() { + PROFILE_MEM(GraphicsCommands); return New(); } @@ -165,6 +169,7 @@ GPUTimerQuery* GPUDeviceNull::CreateTimerQuery() GPUBuffer* GPUDeviceNull::CreateBuffer(const StringView& name) { + PROFILE_MEM(GraphicsBuffers); return New(); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 359ac0993..73eb90755 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -34,6 +34,7 @@ #include "Engine/Engine/CommandLine.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/Threading.h" #include "Engine/Scripting/Enums.h" @@ -229,9 +230,13 @@ static VKAPI_ATTR VkBool32 VKAPI_PTR DebugUtilsCallback(VkDebugUtilsMessageSever const String message(callbackData->pMessage); if (callbackData->pMessageIdName) + { LOG(Info, "[Vulkan] {0} {1}:{2}({3}) {4}", type, severity, callbackData->messageIdNumber, String(callbackData->pMessageIdName), message); + } else + { LOG(Info, "[Vulkan] {0} {1}:{2} {3}", type, severity, callbackData->messageIdNumber, message); + } #if BUILD_DEBUG if (auto* context = (GPUContextVulkan*)GPUDevice::Instance->GetMainContext()) @@ -2095,16 +2100,19 @@ void GPUDeviceVulkan::WaitForGPU() GPUTexture* GPUDeviceVulkan::CreateTexture(const StringView& name) { + PROFILE_MEM(GraphicsTextures); return New(this, name); } GPUShader* GPUDeviceVulkan::CreateShader(const StringView& name) { + PROFILE_MEM(GraphicsShaders); return New(this, name); } GPUPipelineState* GPUDeviceVulkan::CreatePipelineState() { + PROFILE_MEM(GraphicsCommands); return New(this); } @@ -2115,6 +2123,7 @@ GPUTimerQuery* GPUDeviceVulkan::CreateTimerQuery() GPUBuffer* GPUDeviceVulkan::CreateBuffer(const StringView& name) { + PROFILE_MEM(GraphicsBuffers); return New(this, name); } @@ -2135,6 +2144,7 @@ GPUSwapChain* GPUDeviceVulkan::CreateSwapChain(Window* window) GPUConstantBuffer* GPUDeviceVulkan::CreateConstantBuffer(uint32 size, const StringView& name) { + PROFILE_MEM(GraphicsShaders); return New(this, size); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp index f6fbe4838..853fcf693 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp @@ -12,6 +12,7 @@ #include "Engine/Core/Types/DataContainer.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Graphics/PixelFormatExtensions.h" +#include "Engine/Profiler/ProfilerMemory.h" #if PLATFORM_DESKTOP #define VULKAN_UNIFORM_RING_BUFFER_SIZE (24 * 1024 * 1024) @@ -41,6 +42,7 @@ UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device VkResult result = vmaCreateBuffer(_device->Allocator, &bufferInfo, &allocInfo, &_buffer, &_allocation, nullptr); LOG_VULKAN_RESULT(result); _memoryUsage = bufferInfo.size; + PROFILE_MEM_INC(GraphicsCommands, _memoryUsage); // Map buffer result = vmaMapMemory(_device->Allocator, _allocation, (void**)&_mapped); @@ -87,6 +89,7 @@ void UniformBufferUploaderVulkan::OnReleaseGPU() { if (_allocation != VK_NULL_HANDLE) { + PROFILE_MEM_DEC(GraphicsCommands, _memoryUsage); if (_mapped) { vmaUnmapMemory(_device->Allocator, _allocation); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index 18e2cd95c..801ba1fc1 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -12,6 +12,7 @@ #include "Engine/Graphics/GPULimits.h" #include "Engine/Scripting/Enums.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" void BackBufferVulkan::Setup(GPUSwapChainVulkan* window, VkImage backbuffer, PixelFormat format, VkExtent3D extent) { @@ -61,6 +62,7 @@ void GPUSwapChainVulkan::OnReleaseGPU() ReleaseBackBuffer(); // Release data + PROFILE_MEM_DEC(Graphics, _memoryUsage); _currentImageIndex = -1; _semaphoreIndex = 0; _acquiredImageIndex = -1; @@ -76,6 +78,7 @@ void GPUSwapChainVulkan::OnReleaseGPU() _surface = VK_NULL_HANDLE; } _width = _height = 0; + _memoryUsage = 0; } bool GPUSwapChainVulkan::IsFullscreen() @@ -412,6 +415,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) // Estimate memory usage _memoryUsage = 1024 + RenderTools::CalculateTextureMemoryUsage(_format, _width, _height, 1) * _backBuffers.Count(); + PROFILE_MEM_INC(Graphics, _memoryUsage); return false; } diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 7a4d0592c..dc5ecc236 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -14,6 +14,7 @@ #include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/JsonTools.h" struct AxisEvaluation @@ -89,12 +90,14 @@ Array Input::AxisMappings; void InputSettings::Apply() { + PROFILE_MEM(Input); Input::ActionMappings = ActionMappings; Input::AxisMappings = AxisMappings; } void InputSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { + PROFILE_MEM(Input); const auto actionMappings = stream.FindMember("ActionMappings"); if (actionMappings != stream.MemberEnd()) { @@ -615,6 +618,7 @@ float Input::GetAxisRaw(const StringView& name) void Input::SetInputMappingFromSettings(const JsonAssetReference& settings) { + PROFILE_MEM(Input); auto actionMappings = settings.GetInstance()->ActionMappings; ActionMappings.Resize(actionMappings.Count(), false); for (int i = 0; i < actionMappings.Count(); i++) @@ -634,6 +638,7 @@ void Input::SetInputMappingFromSettings(const JsonAssetReference& void Input::SetInputMappingToDefaultSettings() { + PROFILE_MEM(Input); InputSettings* settings = InputSettings::Get(); if (settings) { @@ -696,6 +701,7 @@ Array Input::GetAllAxisConfigsByName(const StringView& name) void Input::SetAxisConfigByName(const StringView& name, AxisConfig& config, bool all) { + PROFILE_MEM(Input); for (int i = 0; i < AxisMappings.Count(); ++i) { auto& mapping = AxisMappings.At(i); @@ -712,6 +718,7 @@ void Input::SetAxisConfigByName(const StringView& name, AxisConfig& config, bool void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const KeyboardKeys positiveButton, const KeyboardKeys negativeButton, bool all) { + PROFILE_MEM(Input); for (int i = 0; i < AxisMappings.Count(); ++i) { auto& mapping = AxisMappings.At(i); @@ -727,6 +734,7 @@ void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all) { + PROFILE_MEM(Input); for (int i = 0; i < AxisMappings.Count(); ++i) { auto& mapping = AxisMappings.At(i); @@ -742,6 +750,7 @@ void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const float gravity, const float deadZone, const float sensitivity, const float scale, const bool snap, bool all) { + PROFILE_MEM(Input); for (int i = 0; i < AxisMappings.Count(); ++i) { auto& mapping = AxisMappings.At(i); @@ -760,6 +769,7 @@ void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, void Input::SetActionConfigByName(const StringView& name, const KeyboardKeys key, bool all) { + PROFILE_MEM(Input); for (int i = 0; i < ActionMappings.Count(); ++i) { auto& mapping = ActionMappings.At(i); @@ -774,6 +784,7 @@ void Input::SetActionConfigByName(const StringView& name, const KeyboardKeys key void Input::SetActionConfigByName(const StringView& name, const MouseButton mouseButton, bool all) { + PROFILE_MEM(Input); for (int i = 0; i < ActionMappings.Count(); ++i) { auto& mapping = ActionMappings.At(i); @@ -788,6 +799,7 @@ void Input::SetActionConfigByName(const StringView& name, const MouseButton mous void Input::SetActionConfigByName(const StringView& name, const GamepadButton gamepadButton, InputGamepadIndex gamepadIndex, bool all) { + PROFILE_MEM(Input); for (int i = 0; i < ActionMappings.Count(); ++i) { auto& mapping = ActionMappings.At(i); @@ -802,6 +814,7 @@ void Input::SetActionConfigByName(const StringView& name, const GamepadButton ga void Input::SetActionConfigByName(const StringView& name, ActionConfig& config, bool all) { + PROFILE_MEM(Input); for (int i = 0; i < ActionMappings.Count(); ++i) { auto& mapping = ActionMappings.At(i); @@ -819,6 +832,7 @@ void Input::SetActionConfigByName(const StringView& name, ActionConfig& config, void InputService::Update() { PROFILE_CPU(); + PROFILE_MEM(Input); const auto frame = Time::Update.TicksCount; const auto dt = Time::Update.UnscaledDeltaTime.GetTotalSeconds(); InputEvents.Clear(); diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 4989f2c49..12039da5e 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1127,9 +1127,13 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) else if (!parent && parentId.IsValid()) { if (_prefabObjectID.IsValid()) + { LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID); + } else + { LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); + } } } } diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index a96ba936a..5bcd98994 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -24,6 +24,7 @@ #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Script.h" #include "Engine/Engine/Time.h" #include "Engine/Scripting/ManagedCLR/MAssembly.h" @@ -248,6 +249,7 @@ void LayersAndTagsSettings::Apply() #define TICK_LEVEL(tickingStage, name) \ PROFILE_CPU_NAMED(name); \ + PROFILE_MEM(Level); \ ScopeLock lock(Level::ScenesLock); \ auto& scenes = Level::Scenes; \ if (!Time::GetGamePaused() && Level::TickEnabled) \ @@ -504,6 +506,7 @@ public: // Note: we don't want to override original scene files PROFILE_CPU_NAMED("Level.ReloadScripts"); + PROFILE_MEM(Level); LOG(Info, "Scripts reloading start"); const auto startTime = DateTime::NowUTC(); @@ -784,6 +787,7 @@ bool LevelImpl::unloadScene(Scene* scene) const auto sceneId = scene->GetID(); PROFILE_CPU_NAMED("Level.UnloadScene"); + PROFILE_MEM(Level); // Fire event CallSceneEvent(SceneEventType::OnSceneUnloading, scene, sceneId); @@ -838,6 +842,7 @@ bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene) LOG(Error, "Missing scene data."); return true; } + PROFILE_MEM(Level); // Parse scene JSON file rapidjson_flax::Document document; @@ -870,6 +875,7 @@ bool Level::loadScene(rapidjson_flax::Document& document, Scene** outScene) bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath) { PROFILE_CPU_NAMED("Level.LoadScene"); + PROFILE_MEM(Level); if (outScene) *outScene = nullptr; #if USE_EDITOR @@ -954,6 +960,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) JobSystem::Execute([&](int32 i) { + PROFILE_MEM(Level); i++; // Start from 1. at index [0] was scene auto& stream = data[i]; auto obj = SceneObjectsFactory::Spawn(context, stream); @@ -1165,6 +1172,7 @@ bool LevelImpl::saveScene(Scene* scene) bool LevelImpl::saveScene(Scene* scene, const String& path) { PROFILE_CPU_NAMED("Level.SaveScene"); + PROFILE_MEM(Level); ASSERT(scene && EnumHasNoneFlags(scene->Flags, ObjectFlags::WasMarkedToDelete)); auto sceneId = scene->GetID(); @@ -1208,6 +1216,7 @@ bool LevelImpl::saveScene(Scene* scene, const String& path) bool LevelImpl::saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, bool prettyJson) { PROFILE_CPU_NAMED("Level.SaveScene"); + PROFILE_MEM(Level); if (prettyJson) { PrettyJsonWriter writerObj(outBuffer); diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 445447cd1..c6f5669a5 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -9,6 +9,7 @@ #include "Engine/Threading/JobSystem.h" #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" ISceneRenderingListener::~ISceneRenderingListener() { @@ -41,6 +42,7 @@ FORCE_INLINE bool FrustumsListCull(const BoundingSphere& bounds, const Array_drawCategory; ScopeLock lock(Locker); auto& list = Actors[category]; @@ -214,6 +217,7 @@ void SceneRendering::RemoveActor(Actor* a, int32& key) void SceneRendering::DrawActorsJob(int32) { PROFILE_CPU(); + PROFILE_MEM(Graphics); auto& mainContext = _drawBatch->GetMainContext(); const auto& view = mainContext.View; if (view.StaticFlagsMask != StaticFlags::None) diff --git a/Source/Engine/Localization/CultureInfo.cpp b/Source/Engine/Localization/CultureInfo.cpp index 4595703af..7d53489b5 100644 --- a/Source/Engine/Localization/CultureInfo.cpp +++ b/Source/Engine/Localization/CultureInfo.cpp @@ -3,6 +3,7 @@ #include "CultureInfo.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/StringView.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Scripting/Types.h" #include "Engine/Scripting/ManagedCLR/MProperty.h" @@ -51,6 +52,7 @@ CultureInfo::CultureInfo(int32 lcid) _data = nullptr; if (lcid == 0) return; + PROFILE_MEM(Localization); if (lcid == 127) { _englishName = TEXT("Invariant Culture"); @@ -88,6 +90,7 @@ CultureInfo::CultureInfo(const StringView& name) CultureInfo::CultureInfo(const StringAnsiView& name) { + PROFILE_MEM(Localization); _data = nullptr; if (name.IsEmpty()) { @@ -160,6 +163,7 @@ bool CultureInfo::operator==(const CultureInfo& other) const void* MUtils::ToManaged(const CultureInfo& value) { #if USE_CSHARP + PROFILE_MEM(Localization); auto scriptingClass = Scripting::GetStaticClass(); CHECK_RETURN(scriptingClass, nullptr); auto cultureInfoToManaged = scriptingClass->GetMethod("CultureInfoToManaged", 1); @@ -182,6 +186,7 @@ CultureInfo MUtils::ToNative(void* value) if (value) lcid = static_cast(value)->lcid; #elif USE_CSHARP + PROFILE_MEM(Localization); const MClass* klass = GetBinaryModuleCorlib()->Assembly->GetClass("System.Globalization.CultureInfo"); if (value && klass) { diff --git a/Source/Engine/Localization/Localization.cpp b/Source/Engine/Localization/Localization.cpp index 00a6a8deb..d1b3a036f 100644 --- a/Source/Engine/Localization/Localization.cpp +++ b/Source/Engine/Localization/Localization.cpp @@ -9,6 +9,7 @@ #include "Engine/Engine/EngineService.h" #include "Engine/Content/Content.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/Serialization.h" #include @@ -171,6 +172,7 @@ String LocalizedString::ToStringPlural(int32 n) const void LocalizationService::OnLocalizationChanged() { PROFILE_CPU(); + PROFILE_MEM(Localization); Instance.LocalizedStringTables.Clear(); Instance.FallbackStringTables.Clear(); @@ -279,6 +281,8 @@ void LocalizationService::OnLocalizationChanged() bool LocalizationService::Init() { + PROFILE_MEM(Localization); + // Use system language as default CurrentLanguage = CurrentCulture = CultureInfo(Platform::GetUserLocaleName()); diff --git a/Source/Engine/Localization/LocalizedStringTable.cpp b/Source/Engine/Localization/LocalizedStringTable.cpp index 84aca852d..e99b87a27 100644 --- a/Source/Engine/Localization/LocalizedStringTable.cpp +++ b/Source/Engine/Localization/LocalizedStringTable.cpp @@ -5,6 +5,7 @@ #include "Engine/Serialization/JsonWriters.h" #include "Engine/Serialization/SerializationFwd.h" #include "Engine/Content/Factories/JsonAssetFactory.h" +#include "Engine/Profiler/ProfilerMemory.h" #if USE_EDITOR #include "Engine/Threading/Threading.h" #include "Engine/Core/Log.h" @@ -20,6 +21,7 @@ LocalizedStringTable::LocalizedStringTable(const SpawnParams& params, const Asse void LocalizedStringTable::AddString(const StringView& id, const StringView& value) { + PROFILE_MEM(Localization); auto& values = Entries[id]; values.Resize(1); values[0] = value; @@ -27,6 +29,7 @@ void LocalizedStringTable::AddString(const StringView& id, const StringView& val void LocalizedStringTable::AddPluralString(const StringView& id, const StringView& value, int32 n) { + PROFILE_MEM(Localization); CHECK(n >= 0 && n < 1024); auto& values = Entries[id]; values.Resize(Math::Max(values.Count(), n + 1)); @@ -57,6 +60,8 @@ String LocalizedStringTable::GetPluralString(const String& id, int32 n) const Asset::LoadResult LocalizedStringTable::loadAsset() { + PROFILE_MEM(Localization); + // Base auto result = JsonAssetBase::loadAsset(); if (result != LoadResult::Ok || IsInternalType()) diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp index c9a56a08a..cb2a4ebee 100644 --- a/Source/Engine/Navigation/NavCrowd.cpp +++ b/Source/Engine/Navigation/NavCrowd.cpp @@ -34,9 +34,13 @@ bool NavCrowd::Init(const NavAgentProperties& agentProperties, int32 maxAgents) if (!navMeshRuntime) { if (NavMeshRuntime::Get()) + { LOG(Error, "Cannot create crowd. Failed to find a navmesh that matches a given agent properties."); + } else + { LOG(Error, "Cannot create crowd. No navmesh is loaded."); + } } #endif return Init(agentProperties.Radius * 3.0f, maxAgents, navMeshRuntime); diff --git a/Source/Engine/Navigation/NavMeshData.cpp b/Source/Engine/Navigation/NavMeshData.cpp index 7dfa597a8..6fbf5e33e 100644 --- a/Source/Engine/Navigation/NavMeshData.cpp +++ b/Source/Engine/Navigation/NavMeshData.cpp @@ -4,6 +4,7 @@ #include "Engine/Core/Log.h" #include "Engine/Serialization/WriteStream.h" #include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Profiler/ProfilerMemory.h" void NavMeshData::Save(WriteStream& stream) { @@ -47,6 +48,7 @@ bool NavMeshData::Load(BytesContainer& data, bool copyData) return true; } MemoryReadStream stream(data.Get(), data.Length()); + PROFILE_MEM(Navigation); // Read header const auto header = stream.Move(); diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index baa8b0320..f90d5efd1 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Random.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/Threading.h" #include #include @@ -312,6 +313,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) if (newTilesCount <= capacity) return; PROFILE_CPU_NAMED("NavMeshRuntime.EnsureCapacity"); + PROFILE_MEM(Navigation); // Navmesh tiles capacity growing rule int32 newCapacity = capacity ? capacity : 32; @@ -380,6 +382,7 @@ void NavMeshRuntime::AddTiles(NavMesh* navMesh) return; auto& data = navMesh->Data; PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles"); + PROFILE_MEM(Navigation); ScopeLock lock(Locker); // Validate data (must match navmesh) or init navmesh to match the tiles options @@ -411,6 +414,7 @@ void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData) ASSERT(navMesh); auto& data = navMesh->Data; PROFILE_CPU_NAMED("NavMeshRuntime.AddTile"); + PROFILE_MEM(Navigation); ScopeLock lock(Locker); // Validate data (must match navmesh) or init navmesh to match the tiles options diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 446464634..1d54550df 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -18,6 +18,7 @@ #include "Engine/Content/Deprecated.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/Serialization.h" #include #include @@ -93,6 +94,7 @@ NavMeshRuntime* NavMeshRuntime::Get(const NavMeshProperties& navMeshProperties, if (!result && createIfMissing) { // Create a new navmesh + PROFILE_MEM(Navigation); result = New(navMeshProperties); NavMeshes.Add(result); } @@ -178,16 +180,20 @@ NavigationService NavigationServiceInstance; void* dtAllocDefault(size_t size, dtAllocHint) { + PROFILE_MEM(Navigation); return Allocator::Allocate(size); } void* rcAllocDefault(size_t size, rcAllocHint) { + PROFILE_MEM(Navigation); return Allocator::Allocate(size); } NavigationSettings::NavigationSettings() { + PROFILE_MEM(Navigation); + // Init navmeshes NavMeshes.Resize(1); auto& navMesh = NavMeshes[0]; diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index af84b8d8c..3dc244bba 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -14,6 +14,7 @@ #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Scripting.h" float NetworkManager::NetworkFPS = 60.0f; @@ -414,6 +415,7 @@ NetworkManagerService NetworkManagerServiceInstance; bool StartPeer() { PROFILE_CPU(); + PROFILE_MEM(Networking); ASSERT_LOW_LAYER(!NetworkManager::Peer); NetworkManager::State = NetworkConnectionState::Connecting; NetworkManager::StateChanged(); @@ -504,6 +506,7 @@ NetworkClient* NetworkManager::GetClient(uint32 clientId) bool NetworkManager::StartServer() { PROFILE_CPU(); + PROFILE_MEM(Networking); Stop(); LOG(Info, "Starting network manager as server"); @@ -529,6 +532,7 @@ bool NetworkManager::StartServer() bool NetworkManager::StartClient() { PROFILE_CPU(); + PROFILE_MEM(Networking); Stop(); LOG(Info, "Starting network manager as client"); @@ -553,6 +557,7 @@ bool NetworkManager::StartClient() bool NetworkManager::StartHost() { PROFILE_CPU(); + PROFILE_MEM(Networking); Stop(); LOG(Info, "Starting network manager as host"); @@ -586,6 +591,7 @@ void NetworkManager::Stop() if (Mode == NetworkManagerMode::Offline && State == NetworkConnectionState::Offline) return; PROFILE_CPU(); + PROFILE_MEM(Networking); LOG(Info, "Stopping network manager"); State = NetworkConnectionState::Disconnecting; @@ -632,6 +638,7 @@ void NetworkManager::Stop() void NetworkKeys::SendPending() { PROFILE_CPU(); + PROFILE_MEM(Networking); ScopeLock lock(Lock); // Add new keys @@ -718,6 +725,7 @@ void NetworkManagerService::Update() if (NetworkManager::Mode == NetworkManagerMode::Offline || (float)(currentTime - LastUpdateTime) < minDeltaTime || !peer) return; PROFILE_CPU(); + PROFILE_MEM(Networking); LastUpdateTime = currentTime; NetworkManager::Frame++; NetworkInternal::NetworkReplicatorPreUpdate(); diff --git a/Source/Engine/Networking/NetworkPeer.cpp b/Source/Engine/Networking/NetworkPeer.cpp index 0f815a1ec..073d3d060 100644 --- a/Source/Engine/Networking/NetworkPeer.cpp +++ b/Source/Engine/Networking/NetworkPeer.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Math/Math.h" #include "Engine/Platform/CPUInfo.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" Array NetworkPeer::Peers; @@ -85,6 +86,7 @@ void NetworkPeer::Shutdown() void NetworkPeer::CreateMessageBuffers() { + PROFILE_MEM(Networking); ASSERT(MessageBuffer == nullptr); const uint32 pageSize = Platform::GetCPUInfo().PageSize; @@ -198,6 +200,8 @@ bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const Net NetworkPeer* NetworkPeer::CreatePeer(const NetworkConfig& config) { + PROFILE_MEM(Networking); + // Validate the address for listen/connect if (config.Address != TEXT("any")) { diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index af478a019..fba916891 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -24,6 +24,7 @@ #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Script.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ScriptingObjectReference.h" @@ -1112,6 +1113,7 @@ void NetworkReplicator::AddSerializer(const ScriptingTypeHandle& typeHandle, Ser { if (!typeHandle) return; + PROFILE_MEM(Networking); const Serializer serializer{ { serialize, deserialize }, { serializeTag, deserializeTag } }; SerializersTable[typeHandle] = serializer; } @@ -1145,6 +1147,7 @@ bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle, serializer.Methods[1] = INetworkSerializable_Script_Deserialize; serializer.Tags[0] = serializer.Tags[1] = nullptr; } + PROFILE_MEM(Networking); SerializersTable.Add(typeHandle, serializer); } else if (const ScriptingTypeHandle baseTypeHandle = typeHandle.GetType().GetBaseType()) @@ -1166,6 +1169,7 @@ void NetworkReplicator::AddObject(ScriptingObject* obj, const ScriptingObject* p { if (!obj || NetworkManager::IsOffline()) return; + PROFILE_MEM(Networking); ScopeLock lock(ObjectsLock); if (Objects.Contains(obj)) return; @@ -1235,6 +1239,7 @@ void NetworkReplicator::SpawnObject(ScriptingObject* obj, const DataContainerGetID()); if (it != Objects.End() && it->Item.Spawned) @@ -1250,6 +1255,7 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj) { if (!obj || NetworkManager::IsOffline()) return; + PROFILE_MEM(Networking); ScopeLock lock(ObjectsLock); const auto it = Objects.Find(obj->GetID()); if (it == Objects.End()) @@ -1524,6 +1530,7 @@ Dictionary NetworkRpcInfo::RPCsTable; NetworkStream* NetworkReplicator::BeginInvokeRPC() { + PROFILE_MEM(Networking); if (CachedWriteStream == nullptr) CachedWriteStream = New(); CachedWriteStream->Initialize(); @@ -1540,6 +1547,7 @@ bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name)); if (!info || !obj || NetworkManager::IsOffline()) return false; + PROFILE_MEM(Networking); ObjectsLock.Lock(); auto& rpc = RpcQueue.AddOne(); rpc.Object = obj; diff --git a/Source/Engine/Networking/NetworkStream.cpp b/Source/Engine/Networking/NetworkStream.cpp index 5c4a367f1..1542c98bd 100644 --- a/Source/Engine/Networking/NetworkStream.cpp +++ b/Source/Engine/Networking/NetworkStream.cpp @@ -4,6 +4,7 @@ #include "INetworkSerializable.h" #include "Engine/Core/Math/Quaternion.h" #include "Engine/Core/Math/Transform.h" +#include "Engine/Profiler/ProfilerMemory.h" // Quaternion quantized for optimized network data size. struct NetworkQuaternion @@ -119,6 +120,7 @@ void NetworkStream::Initialize(uint32 minCapacity) Allocator::Free(_buffer); // Allocate new one + PROFILE_MEM(Networking); _buffer = (byte*)Allocator::Allocate(minCapacity); _length = minCapacity; _allocated = true; @@ -246,6 +248,7 @@ void NetworkStream::WriteBytes(const void* data, uint32 bytes) uint32 newLength = _length != 0 ? _length * 2 : 256; while (newLength < position + bytes) newLength *= 2; + PROFILE_MEM(Networking); byte* newBuf = (byte*)Allocator::Allocate(newLength); if (_buffer && _length) Platform::MemoryCopy(newBuf, _buffer, _length); diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index c1031f4ac..1359dbcf2 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -6,6 +6,7 @@ #include "Engine/Content/Deprecated.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Engine/Time.h" @@ -380,6 +381,7 @@ void ParticleEffect::Sync() Instance.ClearState(); return; } + PROFILE_MEM(Particles); Instance.Sync(system); @@ -498,6 +500,7 @@ void ParticleEffect::CacheModifiedParameters() { if (_parameters.IsEmpty()) return; + PROFILE_MEM(Particles); _parametersOverrides.Clear(); auto& parameters = GetParameters(); for (auto& param : parameters) @@ -516,6 +519,7 @@ void ParticleEffect::ApplyModifiedParameters() { if (_parametersOverrides.IsEmpty()) return; + PROFILE_MEM(Particles); // Parameters getter applies the parameters overrides if (_parameters.IsEmpty()) @@ -658,6 +662,7 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier* // Base Actor::Deserialize(stream, modifier); + PROFILE_MEM(Particles); const auto overridesMember = stream.FindMember("Overrides"); if (overridesMember != stream.MemberEnd()) { diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index 452d4560a..c7bc647bf 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -13,6 +13,7 @@ #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Threading/Threading.h" +#include "Engine/Profiler/ProfilerMemory.h" #if USE_EDITOR #include "ParticleEmitterFunction.h" #include "Engine/ShadersCompilation/Config.h" @@ -41,6 +42,7 @@ ParticleEmitter::ParticleEmitter(const SpawnParams& params, const AssetInfo* inf ParticleEffect* ParticleEmitter::Spawn(Actor* parent, const Transform& transform, float duration, bool autoDestroy) { + PROFILE_MEM(Particles); CHECK_RETURN(!WaitForLoaded(), nullptr); auto system = Content::CreateVirtualAsset(); CHECK_RETURN(system, nullptr); @@ -72,6 +74,7 @@ namespace Asset::LoadResult ParticleEmitter::load() { + PROFILE_MEM(Particles); ConcurrentSystemLocker::WriteScope systemScope(Particles::SystemLocker); // Load the graph diff --git a/Source/Engine/Particles/ParticleEmitterFunction.cpp b/Source/Engine/Particles/ParticleEmitterFunction.cpp index 3aa0ec115..f8aa5c62a 100644 --- a/Source/Engine/Particles/ParticleEmitterFunction.cpp +++ b/Source/Engine/Particles/ParticleEmitterFunction.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Threading/Threading.h" +#include "Engine/Profiler/ProfilerMemory.h" #if USE_EDITOR #include "Engine/Core/Types/DataContainer.h" #include "Engine/Serialization/MemoryWriteStream.h" @@ -41,6 +42,7 @@ ParticleEmitterFunction::ParticleEmitterFunction(const SpawnParams& params, cons Asset::LoadResult ParticleEmitterFunction::load() { + PROFILE_MEM(Particles); ConcurrentSystemLocker::WriteScope systemScope(Particles::SystemLocker); // Load graph diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 7eea5a08d..8354f48cb 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -6,6 +6,7 @@ #include "Engine/Level/Level.h" #include "Engine/Content/Deprecated.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Threading/Threading.h" @@ -146,6 +147,7 @@ bool ParticleSystem::SaveTimeline(const BytesContainer& data) const ParticleEffect* ParticleSystem::Spawn(Actor* parent, const Transform& transform, bool autoDestroy) { + PROFILE_MEM(Particles); CHECK_RETURN(!WaitForLoaded(), nullptr); auto effect = New(); @@ -202,6 +204,7 @@ bool ParticleSystem::Save(const StringView& path) Asset::LoadResult ParticleSystem::load() { + PROFILE_MEM(Particles); Version++; // Get the data chunk diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index e895e0b6d..3cf25eec6 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -16,6 +16,7 @@ #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Threading/TaskGraph.h" @@ -167,6 +168,7 @@ ParticleManagerService ParticleManagerServiceInstance; void Particles::UpdateEffect(ParticleEffect* effect) { + PROFILE_MEM(Particles); UpdateList.Add(effect); } @@ -933,6 +935,7 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe const DrawPass drawModes = view.Pass & effect->DrawModes; if (drawModes == DrawPass::None || SpriteRenderer.Init()) return; + PROFILE_MEM(Particles); Matrix worlds[2]; Matrix::Translation(-renderContext.View.Origin, worlds[0]); // World renderContext.View.GetWorldMatrix(effect->GetTransform(), worlds[1]); // Local @@ -1073,6 +1076,7 @@ void UpdateGPU(RenderTask* task, GPUContext* context) if (GpuUpdateList.IsEmpty()) return; PROFILE_GPU("GPU Particles"); + PROFILE_MEM(Particles); for (ParticleEffect* effect : GpuUpdateList) { @@ -1112,6 +1116,7 @@ void UpdateGPU(RenderTask* task, GPUContext* context) ParticleBuffer* Particles::AcquireParticleBuffer(ParticleEmitter* emitter) { PROFILE_CPU(); + PROFILE_MEM(Particles); ParticleBuffer* result = nullptr; ASSERT(emitter && emitter->IsLoaded()); @@ -1161,6 +1166,7 @@ ParticleBuffer* Particles::AcquireParticleBuffer(ParticleEmitter* emitter) void Particles::RecycleParticleBuffer(ParticleBuffer* buffer) { PROFILE_CPU(); + PROFILE_MEM(Particles); if (buffer->Emitter->EnablePooling && EnableParticleBufferPooling) { // Return to pool @@ -1208,6 +1214,7 @@ void Particles::OnEmitterUnload(ParticleEmitter* emitter) bool ParticleManagerService::Init() { + PROFILE_MEM(Particles); Particles::System = New(); Particles::System->Order = 10000; Engine::UpdateGraph->AddSystem(Particles::System); @@ -1253,6 +1260,7 @@ void ParticleManagerService::Dispose() void ParticlesSystem::Job(int32 index) { PROFILE_CPU_NAMED("Particles.Job"); + PROFILE_MEM(Particles); auto effect = UpdateList[index]; auto& instance = effect->Instance; const auto particleSystem = effect->ParticleSystem.Get(); @@ -1432,6 +1440,7 @@ void ParticlesSystem::PostExecute(TaskGraph* graph) if (!Active) return; PROFILE_CPU_NAMED("Particles.PostExecute"); + PROFILE_MEM(Particles); // Cleanup Particles::SystemLocker.End(false); diff --git a/Source/Engine/Particles/ParticlesData.cpp b/Source/Engine/Particles/ParticlesData.cpp index dcdef46d7..10988fd34 100644 --- a/Source/Engine/Particles/ParticlesData.cpp +++ b/Source/Engine/Particles/ParticlesData.cpp @@ -5,6 +5,7 @@ #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/DynamicBuffer.h" +#include "Engine/Profiler/ProfilerMemory.h" ParticleBuffer::ParticleBuffer() { @@ -23,6 +24,7 @@ ParticleBuffer::~ParticleBuffer() bool ParticleBuffer::Init(ParticleEmitter* emitter) { + PROFILE_MEM(Particles); ASSERT(emitter && emitter->IsLoaded()); Version = emitter->Graph.Version; diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 889a0874e..037dc0000 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -11,6 +11,7 @@ #include "Engine/Physics/PhysicsBackend.h" #include "Engine/Physics/PhysicsScene.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Actors/AnimatedModel.h" #include "Engine/Level/Scene/SceneRendering.h" @@ -132,6 +133,7 @@ Array Cloth::GetParticles() const if (_cloth) { PROFILE_CPU(); + PROFILE_MEM(Physics); PhysicsBackend::LockClothParticles(_cloth); const Span particles = PhysicsBackend::GetClothParticles(_cloth); result.Resize(particles.Length()); @@ -148,6 +150,7 @@ Array Cloth::GetParticles() const void Cloth::SetParticles(Span value) { PROFILE_CPU(); + PROFILE_MEM(Physics); #if USE_CLOTH_SANITY_CHECKS { // Sanity check @@ -177,6 +180,7 @@ Span Cloth::GetPaint() const void Cloth::SetPaint(Span value) { PROFILE_CPU(); + PROFILE_MEM(Physics); #if USE_CLOTH_SANITY_CHECKS { // Sanity check @@ -302,6 +306,7 @@ void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { Actor::Deserialize(stream, modifier); + PROFILE_MEM(Physics); DESERIALIZE_MEMBER(Mesh, _mesh); _mesh.Actor = nullptr; // Don't store this reference DESERIALIZE_MEMBER(Force, _forceSettings); @@ -536,6 +541,7 @@ bool Cloth::CreateCloth() { #if WITH_CLOTH PROFILE_CPU(); + PROFILE_MEM(Physics); // Skip if all vertices are fixed so cloth sim doesn't make sense if (_paint.HasItems()) @@ -631,6 +637,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) if (_paint.IsEmpty()) return; PROFILE_CPU(); + PROFILE_MEM(Physics); // Get mesh data const ModelInstanceActor::MeshReference mesh = GetMesh(); @@ -918,6 +925,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat return; #if WITH_CLOTH PROFILE_CPU_NAMED("Cloth"); + PROFILE_MEM(Physics); PhysicsBackend::LockClothParticles(_cloth); const Span particles = PhysicsBackend::GetClothParticles(_cloth); auto vbCount = (uint32)mesh->GetVertexCount(); diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp index 2cf81e228..ea020b5c5 100644 --- a/Source/Engine/Physics/Actors/SplineRopeBody.cpp +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -7,6 +7,7 @@ #include "Engine/Physics/PhysicsScene.h" #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/Serialization.h" SplineRopeBody::SplineRopeBody(const SpawnParams& params) @@ -19,6 +20,7 @@ void SplineRopeBody::Tick() if (!_spline || _spline->GetSplinePointsCount() < 2) return; PROFILE_CPU(); + PROFILE_MEM(Physics); // Cache data const Vector3 gravity = GetPhysicsScene()->GetGravity() * GravityScale; diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index 4522d6862..bbf3f4f91 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -10,11 +10,13 @@ #include "Engine/Graphics/Models/MeshAccessor.h" #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Core/Log.h" bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::SerializedOptions& outputOptions, BytesContainer& outputData) { PROFILE_CPU(); + PROFILE_MEM(Physics); int32 convexVertexLimit = Math::Clamp(arg.ConvexVertexLimit, CONVEX_VERTEX_MIN, CONVEX_VERTEX_MAX); if (arg.ConvexVertexLimit == 0) convexVertexLimit = CONVEX_VERTEX_MAX; diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp index e7e02c464..c65ea8a6b 100644 --- a/Source/Engine/Physics/CollisionData.cpp +++ b/Source/Engine/Physics/CollisionData.cpp @@ -9,6 +9,7 @@ #include "Engine/Physics/PhysicsBackend.h" #include "Engine/Physics/CollisionCooking.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/Threading.h" REGISTER_BINARY_ASSET(CollisionData, "FlaxEngine.CollisionData", true); @@ -35,6 +36,7 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i return true; } PROFILE_CPU(); + PROFILE_MEM(Physics); // Prepare CollisionCooking::Argument arg; @@ -64,6 +66,7 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i bool CollisionData::CookCollision(CollisionDataType type, const Span& vertices, const Span& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { PROFILE_CPU(); + PROFILE_MEM(Physics); CHECK_RETURN(vertices.Length() != 0, true); CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true); ModelData modelData; @@ -78,6 +81,7 @@ bool CollisionData::CookCollision(CollisionDataType type, const Span& ve bool CollisionData::CookCollision(CollisionDataType type, const Span& vertices, const Span& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { PROFILE_CPU(); + PROFILE_MEM(Physics); CHECK_RETURN(vertices.Length() != 0, true); CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true); ModelData modelData; @@ -99,6 +103,7 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelData* modelData, return true; } PROFILE_CPU(); + PROFILE_MEM(Physics); // Prepare CollisionCooking::Argument arg; @@ -180,6 +185,7 @@ bool CollisionData::GetModelTriangle(uint32 faceIndex, MeshBase*& mesh, uint32& void CollisionData::ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const { PROFILE_CPU(); + PROFILE_MEM(Physics); vertexBuffer.Clear(); indexBuffer.Clear(); @@ -197,6 +203,7 @@ const Array& CollisionData::GetDebugLines() if (_hasMissingDebugLines && IsLoaded()) { PROFILE_CPU(); + PROFILE_MEM(Physics); ScopeLock lock(Locker); _hasMissingDebugLines = false; @@ -250,6 +257,8 @@ Asset::LoadResult CollisionData::load() CollisionData::LoadResult CollisionData::load(const SerializedOptions* options, byte* dataPtr, int32 dataSize) { + PROFILE_MEM(Physics); + // Load options _options.Type = options->Type; _options.Model = options->Model; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index e4636a568..5023b69c7 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -24,6 +24,7 @@ #include "Engine/Platform/CPUInfo.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/WriteStream.h" #include #include @@ -117,6 +118,7 @@ class AllocatorPhysX : public PxAllocatorCallback void* allocate(size_t size, const char* typeName, const char* filename, int line) override { ASSERT(size < 1024 * 1024 * 1024); // Prevent invalid allocation size + PROFILE_MEM(Physics); return Allocator::Allocate(size, 16); } @@ -725,6 +727,7 @@ void ScenePhysX::UpdateVehicles(float dt) if (WheelVehicles.IsEmpty()) return; PROFILE_CPU_NAMED("Physics.Vehicles"); + PROFILE_MEM(Physics); // Update vehicles steering WheelVehiclesCache.Clear(); @@ -1861,6 +1864,7 @@ void PhysicsBackend::DestroyScene(void* scene) void PhysicsBackend::StartSimulateScene(void* scene, float dt) { + PROFILE_MEM(Physics); auto scenePhysX = (ScenePhysX*)scene; const auto& settings = *PhysicsSettings::Get(); @@ -1895,6 +1899,7 @@ void PhysicsBackend::StartSimulateScene(void* scene, float dt) void PhysicsBackend::EndSimulateScene(void* scene) { + PROFILE_MEM(Physics); auto scenePhysX = (ScenePhysX*)scene; { @@ -3880,6 +3885,7 @@ void PhysicsBackend::RemoveVehicle(void* scene, WheeledVehicle* actor) void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) { PROFILE_CPU(); + PROFILE_MEM(Physics); #if USE_CLOTH_SANITY_CHECKS { // Sanity check diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index b35538711..73135c3a3 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -6,6 +6,7 @@ #include "Engine/Physics/Colliders/Collider.h" #include "Engine/Physics/Joints/Joint.h" #include "Engine/Physics/Actors/RigidBody.h" +#include "Engine/Profiler/ProfilerMemory.h" #include #include @@ -91,6 +92,7 @@ void SimulationEventCallback::OnJointRemoved(Joint* joint) void SimulationEventCallback::onConstraintBreak(PxConstraintInfo* constraints, PxU32 count) { + PROFILE_MEM(Physics); for (uint32 i = 0; i < count; i++) { PxJoint* joint = reinterpret_cast(constraints[i].externalReference); @@ -114,6 +116,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c // Skip sending events to removed actors if (pairHeader.flags & (PxContactPairHeaderFlag::eREMOVED_ACTOR_0 | PxContactPairHeaderFlag::eREMOVED_ACTOR_1)) return; + PROFILE_MEM(Physics); Collision c; PxContactPairExtraDataIterator j(pairHeader.extraDataStream, pairHeader.extraDataStreamSize); @@ -185,6 +188,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c void SimulationEventCallback::onTrigger(PxTriggerPair* pairs, PxU32 count) { + PROFILE_MEM(Physics); for (PxU32 i = 0; i < count; i++) { const PxTriggerPair& pair = pairs[i]; diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 6b48bc157..ecd7c1093 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -10,6 +10,7 @@ #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Threading/Threading.h" @@ -117,6 +118,8 @@ PhysicalMaterial::~PhysicalMaterial() bool PhysicsService::Init() { + PROFILE_MEM(Physics); + // Initialize backend if (PhysicsBackend::Init()) return true; @@ -153,6 +156,7 @@ void PhysicsService::Dispose() PhysicsScene* Physics::FindOrCreateScene(const StringView& name) { + PROFILE_MEM(Physics); auto scene = FindScene(name); if (scene == nullptr) { @@ -244,6 +248,7 @@ bool Physics::IsDuringSimulation() void Physics::FlushRequests() { PROFILE_CPU_NAMED("Physics.FlushRequests"); + PROFILE_MEM(Physics); for (PhysicsScene* scene : Scenes) PhysicsBackend::FlushRequests(scene->GetPhysicsScene()); PhysicsBackend::FlushRequests(); @@ -492,6 +497,7 @@ PhysicsStatistics PhysicsScene::GetStatistics() const bool PhysicsScene::Init(const StringView& name, const PhysicsSettings& settings) { + PROFILE_MEM(Physics); if (_scene) { PhysicsBackend::DestroyScene(_scene); diff --git a/Source/Engine/Platform/Base/WindowBase.cpp b/Source/Engine/Platform/Base/WindowBase.cpp index de64dfd94..3bd34a1a0 100644 --- a/Source/Engine/Platform/Base/WindowBase.cpp +++ b/Source/Engine/Platform/Base/WindowBase.cpp @@ -10,6 +10,7 @@ #include "Engine/Platform/IGuiData.h" #include "Engine/Scripting/ScriptingType.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/ManagedCLR/MException.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" @@ -204,6 +205,7 @@ void WindowBase::SetRenderingEnabled(bool value) void WindowBase::OnCharInput(Char c) { PROFILE_CPU_NAMED("GUI.OnCharInput"); + PROFILE_MEM(UI); CharInput(c); INVOKE_EVENT_PARAMS_1(OnCharInput, &c); } @@ -211,6 +213,7 @@ void WindowBase::OnCharInput(Char c) void WindowBase::OnKeyDown(KeyboardKeys key) { PROFILE_CPU_NAMED("GUI.OnKeyDown"); + PROFILE_MEM(UI); KeyDown(key); INVOKE_EVENT_PARAMS_1(OnKeyDown, &key); } @@ -218,6 +221,7 @@ void WindowBase::OnKeyDown(KeyboardKeys key) void WindowBase::OnKeyUp(KeyboardKeys key) { PROFILE_CPU_NAMED("GUI.OnKeyUp"); + PROFILE_MEM(UI); KeyUp(key); INVOKE_EVENT_PARAMS_1(OnKeyUp, &key); } @@ -225,6 +229,7 @@ void WindowBase::OnKeyUp(KeyboardKeys key) void WindowBase::OnMouseDown(const Float2& mousePosition, MouseButton button) { PROFILE_CPU_NAMED("GUI.OnMouseDown"); + PROFILE_MEM(UI); MouseDown(mousePosition, button); INVOKE_EVENT_PARAMS_2(OnMouseDown, (void*)&mousePosition, &button); } @@ -232,6 +237,7 @@ void WindowBase::OnMouseDown(const Float2& mousePosition, MouseButton button) void WindowBase::OnMouseUp(const Float2& mousePosition, MouseButton button) { PROFILE_CPU_NAMED("GUI.OnMouseUp"); + PROFILE_MEM(UI); MouseUp(mousePosition, button); INVOKE_EVENT_PARAMS_2(OnMouseUp, (void*)&mousePosition, &button); } @@ -239,6 +245,7 @@ void WindowBase::OnMouseUp(const Float2& mousePosition, MouseButton button) void WindowBase::OnMouseDoubleClick(const Float2& mousePosition, MouseButton button) { PROFILE_CPU_NAMED("GUI.OnMouseDoubleClick"); + PROFILE_MEM(UI); MouseDoubleClick(mousePosition, button); INVOKE_EVENT_PARAMS_2(OnMouseDoubleClick, (void*)&mousePosition, &button); } @@ -246,6 +253,7 @@ void WindowBase::OnMouseDoubleClick(const Float2& mousePosition, MouseButton but void WindowBase::OnMouseWheel(const Float2& mousePosition, float delta) { PROFILE_CPU_NAMED("GUI.OnMouseWheel"); + PROFILE_MEM(UI); MouseWheel(mousePosition, delta); INVOKE_EVENT_PARAMS_2(OnMouseWheel, (void*)&mousePosition, &delta); } @@ -253,6 +261,7 @@ void WindowBase::OnMouseWheel(const Float2& mousePosition, float delta) void WindowBase::OnMouseMove(const Float2& mousePosition) { PROFILE_CPU_NAMED("GUI.OnMouseMove"); + PROFILE_MEM(UI); MouseMove(mousePosition); INVOKE_EVENT_PARAMS_1(OnMouseMove, (void*)&mousePosition); } @@ -260,6 +269,7 @@ void WindowBase::OnMouseMove(const Float2& mousePosition) void WindowBase::OnMouseLeave() { PROFILE_CPU_NAMED("GUI.OnMouseLeave"); + PROFILE_MEM(UI); MouseLeave(); INVOKE_EVENT_PARAMS_0(OnMouseLeave); } @@ -267,6 +277,7 @@ void WindowBase::OnMouseLeave() void WindowBase::OnTouchDown(const Float2& pointerPosition, int32 pointerId) { PROFILE_CPU_NAMED("GUI.OnTouchDown"); + PROFILE_MEM(UI); TouchDown(pointerPosition, pointerId); INVOKE_EVENT_PARAMS_2(OnTouchDown, (void*)&pointerPosition, &pointerId); } @@ -274,6 +285,7 @@ void WindowBase::OnTouchDown(const Float2& pointerPosition, int32 pointerId) void WindowBase::OnTouchMove(const Float2& pointerPosition, int32 pointerId) { PROFILE_CPU_NAMED("GUI.OnTouchMove"); + PROFILE_MEM(UI); TouchMove(pointerPosition, pointerId); INVOKE_EVENT_PARAMS_2(OnTouchMove, (void*)&pointerPosition, &pointerId); } @@ -281,6 +293,7 @@ void WindowBase::OnTouchMove(const Float2& pointerPosition, int32 pointerId) void WindowBase::OnTouchUp(const Float2& pointerPosition, int32 pointerId) { PROFILE_CPU_NAMED("GUI.OnTouchUp"); + PROFILE_MEM(UI); TouchUp(pointerPosition, pointerId); INVOKE_EVENT_PARAMS_2(OnTouchUp, (void*)&pointerPosition, &pointerId); } @@ -391,6 +404,7 @@ bool WindowBase::GetMouseButtonUp(MouseButton button) const void WindowBase::OnShow() { PROFILE_CPU_NAMED("GUI.OnShow"); + PROFILE_MEM(UI); INVOKE_EVENT_PARAMS_0(OnShow); Shown(); } @@ -398,10 +412,13 @@ void WindowBase::OnShow() void WindowBase::OnResize(int32 width, int32 height) { PROFILE_CPU_NAMED("GUI.OnResize"); + PROFILE_MEM_BEGIN(Graphics); if (_swapChain) _swapChain->Resize(width, height); if (RenderTask) RenderTask->Resize(width, height); + PROFILE_MEM_END(); + PROFILE_MEM(UI); Resized({ static_cast(width), static_cast(height) }); INVOKE_EVENT_PARAMS_2(OnResize, &width, &height); } @@ -453,6 +470,7 @@ void WindowBase::OnLostFocus() void WindowBase::OnUpdate(float dt) { PROFILE_CPU_NAMED("GUI.OnUpdate"); + PROFILE_MEM(UI); Update(dt); INVOKE_EVENT_PARAMS_1(OnUpdate, &dt); } @@ -460,6 +478,7 @@ void WindowBase::OnUpdate(float dt) void WindowBase::OnDraw() { PROFILE_CPU_NAMED("GUI.OnDraw"); + PROFILE_MEM(UI); INVOKE_EVENT_PARAMS_0(OnDraw); Draw(); } @@ -467,6 +486,7 @@ void WindowBase::OnDraw() bool WindowBase::InitSwapChain() { // Setup swapchain + PROFILE_MEM(Graphics); if (_swapChain == nullptr) { _swapChain = GPUDevice::Instance->CreateSwapChain((Window*)this); diff --git a/Source/Engine/Platform/Unix/UnixThread.cpp b/Source/Engine/Platform/Unix/UnixThread.cpp index c58c009be..ff6e61b2a 100644 --- a/Source/Engine/Platform/Unix/UnixThread.cpp +++ b/Source/Engine/Platform/Unix/UnixThread.cpp @@ -4,6 +4,9 @@ #include "UnixThread.h" #include "Engine/Core/Log.h" +#if PLATFORM_APPLE_FAMILY +#include "Engine/Utilities/StringConverter.h" +#endif #include "Engine/Threading/IRunnable.h" #include "Engine/Threading/ThreadRegistry.h" @@ -29,7 +32,8 @@ void* UnixThread::ThreadProc(void* pThis) #if PLATFORM_APPLE_FAMILY // Apple doesn't support creating named thread so assign name here { - pthread_setname_np(StringAnsi(thread->GetName()).Get()); + const String& name = thread->GetName(); + pthread_setname_np(StringAsANSI<>(name.Get(), name.Length()).Get()); } #endif const int32 exitCode = thread->Run(); diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp index ea50d2183..0e7ac1566 100644 --- a/Source/Engine/Profiler/ProfilingTools.cpp +++ b/Source/Engine/Profiler/ProfilingTools.cpp @@ -33,6 +33,7 @@ ProfilingToolsService ProfilingToolsServiceInstance; void ProfilingToolsService::Update() { ZoneScoped; + PROFILE_MEM(Profiler); // Capture stats { diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 057265aae..251781c99 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -597,6 +597,8 @@ void OnGUIShaderReloading(Asset* obj) bool Render2DService::Init() { + PROFILE_MEM(UI); + // GUI Shader GUIShader = Content::LoadAsyncInternal(TEXT("Shaders/GUI")); if (GUIShader == nullptr) diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 97a157355..2a6540da5 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -626,6 +626,7 @@ void RenderList::BuildObjectsBuffer() if (count == 0) return; PROFILE_CPU(); + PROFILE_MEM(GraphicsCommands); ObjectBuffer.Data.Resize(count * sizeof(ShaderObjectData)); auto* src = (const DrawCall*)DrawCalls.Get(); auto* dst = (ShaderObjectData*)ObjectBuffer.Data.Get(); @@ -648,6 +649,7 @@ void RenderList::BuildObjectsBuffer() void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer& drawCalls, DrawPass pass, bool stable) { PROFILE_CPU(); + PROFILE_MEM(GraphicsCommands); const auto* drawCallsData = drawCalls.Get(); const auto* listData = list.Indices.Get(); const int32 listSize = list.Indices.Count(); @@ -754,6 +756,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL if (list.IsEmpty()) return; PROFILE_GPU_CPU("Drawing"); + PROFILE_MEM(GraphicsCommands); const auto* drawCallsData = drawCallsList->DrawCalls.Get(); const auto* listData = list.Indices.Get(); const auto* batchesData = list.Batches.Get(); diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 26f3a636e..56c78600b 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -36,6 +36,7 @@ #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Threading/JobSystem.h" +#include "Engine/Profiler/ProfilerMemory.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/QuadOverdrawPass.h" @@ -68,6 +69,8 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont bool RendererService::Init() { + PROFILE_MEM(Graphics); + // Register passes PassList.Add(GBufferPass::Instance()); PassList.Add(ShadowsPass::Instance()); diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 8d61e2bb5..3030b041d 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Utilities.h" #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "ManagedCLR/MAssembly.h" #include "ManagedCLR/MClass.h" #include "ManagedCLR/MMethod.h" @@ -762,6 +763,8 @@ ManagedBinaryModule* ManagedBinaryModule::GetModule(const MAssembly* assembly) ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSpawnParams& params) { + PROFILE_MEM(Scripting); + // Create native object ScriptingTypeHandle managedTypeHandle = params.Type; const ScriptingType* managedTypePtr = &managedTypeHandle.GetType(); @@ -932,6 +935,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) { #if !COMPILE_WITHOUT_CSHARP PROFILE_CPU(); + PROFILE_MEM(Scripting); ASSERT(ClassToTypeIndex.IsEmpty()); ScopeLock lock(Locker); @@ -1028,6 +1032,7 @@ void ManagedBinaryModule::InitType(MClass* mclass) const StringAnsi& typeName = mclass->GetFullName(); if (TypeNameToTypeIndex.ContainsKey(typeName)) return; + PROFILE_MEM(Scripting); // Find first native base C++ class of this C# class MClass* baseClass = mclass->GetBaseClass(); @@ -1057,9 +1062,13 @@ void ManagedBinaryModule::InitType(MClass* mclass) if (baseType.TypeIndex == -1 || baseType.Module == nullptr) { if (baseType.Module) + { LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(baseClass->GetFullName()), baseType.Module->GetName().ToString()); + } else + { LOG(Error, "Missing base class for managed class {0} from unknown assembly.", String(baseClass->GetFullName())); + } return; } @@ -1183,6 +1192,7 @@ void ManagedBinaryModule::OnUnloading(MAssembly* assembly) void ManagedBinaryModule::OnUnloaded(MAssembly* assembly) { PROFILE_CPU(); + PROFILE_MEM(Scripting); // Clear managed-only types Types.Resize(_firstManagedTypeIndex); @@ -1495,9 +1505,13 @@ bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Va if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(parentClass)) { if (!instanceObject) + { LOG(Error, "Failed to get '{0}.{1}' without object instance", String(parentClass->GetFullName()), String(name)); + } else + { LOG(Error, "Failed to get '{0}.{1}' with invalid object instance of type '{2}'", String(parentClass->GetFullName()), String(name), String(MUtils::GetClassFullname(instanceObject))); + } return true; } } @@ -1553,9 +1567,13 @@ bool ManagedBinaryModule::SetFieldValue(void* field, const Variant& instance, Va if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(parentClass)) { if (!instanceObject) + { LOG(Error, "Failed to set '{0}.{1}' without object instance", String(parentClass->GetFullName()), String(name)); + } else + { LOG(Error, "Failed to set '{0}.{1}' with invalid object instance of type '{2}'", String(parentClass->GetFullName()), String(name), String(MUtils::GetClassFullname(instanceObject))); + } return true; } } diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index db184afd9..8761c309c 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -14,6 +14,7 @@ #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Debug/Exceptions/FileNotFoundException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" @@ -80,6 +81,7 @@ bool MAssembly::Load(const String& assemblyPath, const StringView& nativePath) if (IsLoaded()) return false; PROFILE_CPU(); + PROFILE_MEM(Scripting); ZoneText(*assemblyPath, assemblyPath.Length()); Stopwatch stopwatch; diff --git a/Source/Engine/Scripting/Plugins/PluginManager.cpp b/Source/Engine/Scripting/Plugins/PluginManager.cpp index 7645d9bdd..c040acfbb 100644 --- a/Source/Engine/Scripting/Plugins/PluginManager.cpp +++ b/Source/Engine/Scripting/Plugins/PluginManager.cpp @@ -11,6 +11,7 @@ #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Engine/EngineService.h" #include "Engine/Core/Log.h" #include "Engine/Scripting/ManagedCLR/MField.h" @@ -186,6 +187,7 @@ void PluginManagerService::InvokeDeinitialize(Plugin* plugin) void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly) { PROFILE_CPU_NAMED("Load Assembly Plugins"); + PROFILE_MEM(Scripting); const auto gamePluginClass = GamePlugin::GetStaticClass(); if (gamePluginClass == nullptr) @@ -318,6 +320,7 @@ void PluginManagerImpl::InitializePlugins() if (EditorPlugins.Count() + GamePlugins.Count() == 0) return; PROFILE_CPU_NAMED("InitializePlugins"); + PROFILE_MEM(Scripting); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); @@ -345,6 +348,7 @@ void PluginManagerImpl::DeinitializePlugins() if (EditorPlugins.Count() + GamePlugins.Count() == 0) return; PROFILE_CPU_NAMED("DeinitializePlugins"); + PROFILE_MEM(Scripting); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); @@ -375,6 +379,7 @@ void PluginManagerImpl::DeinitializePlugins() bool PluginManagerService::Init() { Initialized = false; + PROFILE_MEM(Scripting); // Process already loaded modules for (auto module : BinaryModule::GetModules()) @@ -472,6 +477,7 @@ Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type) void PluginManager::InitializeGamePlugins() { PROFILE_CPU(); + PROFILE_MEM(Scripting); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); @@ -488,6 +494,7 @@ void PluginManager::InitializeGamePlugins() void PluginManager::DeinitializeGamePlugins() { PROFILE_CPU(); + PROFILE_MEM(Scripting); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 106736d6b..d56c0e5ea 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -29,6 +29,7 @@ #include "Engine/Scripting/BinaryModule.h" #include "Engine/Engine/Globals.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/Threading.h" #include "Engine/Debug/Exceptions/CLRInnerException.h" #if DOTNET_HOST_CORECLR @@ -281,6 +282,7 @@ void MCore::UnloadDomain(const StringAnsi& domainName) bool MCore::LoadEngine() { PROFILE_CPU(); + PROFILE_MEM(Scripting); // Initialize hostfxr if (InitHostfxr()) @@ -735,6 +737,7 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const if (_hasCachedClasses || !IsLoaded()) return _classes; PROFILE_CPU(); + PROFILE_MEM(Scripting); Stopwatch stopwatch; #if TRACY_ENABLE @@ -796,6 +799,7 @@ void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullnam DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle) { + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); MAssembly* assembly = GetAssembly(assemblyHandle); if (assembly == nullptr) @@ -831,6 +835,7 @@ bool MAssembly::LoadCorlib() if (IsLoaded()) return false; PROFILE_CPU(); + PROFILE_MEM(Scripting); #if TRACY_ENABLE const StringAnsiView name("Corlib"); ZoneText(*name, name.Length()); @@ -1056,6 +1061,7 @@ const Array& MClass::GetMethods() const { if (_hasCachedMethods) return _methods; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); if (_hasCachedMethods) return _methods; @@ -1093,6 +1099,7 @@ const Array& MClass::GetFields() const { if (_hasCachedFields) return _fields; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); if (_hasCachedFields) return _fields; @@ -1119,6 +1126,7 @@ const Array& MClass::GetEvents() const { if (_hasCachedEvents) return _events; + PROFILE_MEM(Scripting); // TODO: implement MEvent in .NET @@ -1141,6 +1149,7 @@ const Array& MClass::GetProperties() const { if (_hasCachedProperties) return _properties; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); if (_hasCachedProperties) return _properties; @@ -1167,6 +1176,7 @@ const Array& MClass::GetInterfaces() const { if (_hasCachedInterfaces) return _interfaces; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); if (_hasCachedInterfaces) return _interfaces; @@ -1206,6 +1216,7 @@ const Array& MClass::GetAttributes() const { if (_hasCachedAttributes) return _attributes; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1388,6 +1399,7 @@ const Array& MField::GetAttributes() const { if (_hasCachedAttributes) return _attributes; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1450,6 +1462,7 @@ void MMethod::CacheSignature() const ScopeLock lock(BinaryModule::Locker); if (_hasCachedSignature) return; + PROFILE_MEM(Scripting); static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType")); static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes")); @@ -1550,6 +1563,7 @@ const Array& MMethod::GetAttributes() const { if (_hasCachedAttributes) return _attributes; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1628,6 +1642,7 @@ const Array& MProperty::GetAttributes() const { if (_hasCachedAttributes) return _attributes; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1658,6 +1673,7 @@ MClass* GetOrCreateClass(MType* typeHandle) { if (!typeHandle) return nullptr; + PROFILE_MEM(Scripting); ScopeLock lock(BinaryModule::Locker); MClass* klass; if (!CachedClassHandles.TryGet(typeHandle, klass)) @@ -1781,9 +1797,13 @@ bool InitHostfxr() if (hostfxr == nullptr) { if (FileSystem::FileExists(path)) + { LOG(Fatal, "Failed to load hostfxr library, possible platform/architecture mismatch with the library. See log for more information. ({0})", path); + } else + { LOG(Fatal, "Failed to load hostfxr library ({0})", path); + } return true; } hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_runtime_config"); diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 593092b8f..8c27dc09f 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -33,6 +33,7 @@ #include "Engine/Graphics/RenderTask.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" extern void registerFlaxEngineInternalCalls(); @@ -173,6 +174,7 @@ void onEngineUnloading(MAssembly* assembly); bool ScriptingService::Init() { + PROFILE_MEM(Scripting); Stopwatch stopwatch; // Initialize managed runtime @@ -254,30 +256,35 @@ void ScriptingService::Update() void ScriptingService::LateUpdate() { PROFILE_CPU_NAMED("Scripting::LateUpdate"); + PROFILE_MEM(Scripting); INVOKE_EVENT(LateUpdate); } void ScriptingService::FixedUpdate() { PROFILE_CPU_NAMED("Scripting::FixedUpdate"); + PROFILE_MEM(Scripting); INVOKE_EVENT(FixedUpdate); } void ScriptingService::LateFixedUpdate() { PROFILE_CPU_NAMED("Scripting::LateFixedUpdate"); + PROFILE_MEM(Scripting); INVOKE_EVENT(LateFixedUpdate); } void ScriptingService::Draw() { PROFILE_CPU_NAMED("Scripting::Draw"); + PROFILE_MEM(Scripting); INVOKE_EVENT(Draw); } void ScriptingService::BeforeExit() { PROFILE_CPU_NAMED("Scripting::BeforeExit"); + PROFILE_MEM(Scripting); INVOKE_EVENT(Exit); } @@ -306,6 +313,7 @@ void Scripting::ProcessBuildInfoPath(String& path, const String& projectFolderPa bool Scripting::LoadBinaryModules(const String& path, const String& projectFolderPath) { PROFILE_CPU_NAMED("LoadBinaryModules"); + PROFILE_MEM(Scripting); LOG(Info, "Loading binary modules from build info file {0}", path); // Read file contents @@ -482,6 +490,7 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde bool Scripting::Load() { PROFILE_CPU(); + PROFILE_MEM(Scripting); // Note: this action can be called from main thread (due to Mono problems with assemblies actions from other threads) ASSERT(IsInMainThread()); ScopeLock lock(BinaryModule::Locker); @@ -1034,6 +1043,7 @@ bool Scripting::IsTypeFromGameScripts(const MClass* type) void Scripting::RegisterObject(ScriptingObject* obj) { + PROFILE_MEM(Scripting); const Guid id = obj->GetID(); ScopeLock lock(_objectsLocker); @@ -1116,6 +1126,7 @@ bool initFlaxEngine() void onEngineLoaded(MAssembly* assembly) { + PROFILE_MEM(Scripting); if (initFlaxEngine()) { LOG(Fatal, "Failed to initialize Flax Engine runtime."); diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 624a3f0e9..eb9afdeae 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -745,9 +745,13 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject* if (!skipLog) { if (klass) + { LOG(Warning, "Unable to find scripting object with ID={0} of type {1}", *id, String(klass->GetFullName())); + } else + { LOG(Warning, "Unable to find scripting object with ID={0}", *id); + } LogContext::Print(LogType::Warning); } return nullptr; diff --git a/Source/Engine/Streaming/Streaming.cpp b/Source/Engine/Streaming/Streaming.cpp index 2d233edc9..5ddbd0996 100644 --- a/Source/Engine/Streaming/Streaming.cpp +++ b/Source/Engine/Streaming/Streaming.cpp @@ -7,6 +7,7 @@ #include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/Threading.h" #include "Engine/Threading/TaskGraph.h" #include "Engine/Threading/Task.h" @@ -55,6 +56,7 @@ Array> Streaming::TextureGroups; void StreamingSettings::Apply() { + PROFILE_MEM(ContentStreaming); Streaming::TextureGroups = TextureGroups; SAFE_DELETE_GPU_RESOURCES(TextureGroupSamplers); TextureGroupSamplers.Resize(TextureGroups.Count(), false); @@ -91,6 +93,7 @@ void StreamableResource::StartStreaming(bool isDynamic) _isDynamic = isDynamic; if (!_isStreaming) { + PROFILE_MEM(ContentStreaming); _isStreaming = true; ResourcesLock.Lock(); Resources.Add(this); @@ -201,6 +204,7 @@ void UpdateResource(StreamableResource* resource, double currentTime) bool StreamingService::Init() { + PROFILE_MEM(ContentStreaming); System = New(); Engine::UpdateGraph->AddSystem(System); return false; @@ -217,6 +221,7 @@ void StreamingService::BeforeExit() void StreamingSystem::Job(int32 index) { PROFILE_CPU_NAMED("Streaming.Job"); + PROFILE_MEM(ContentStreaming); // TODO: use streaming settings const double ResourceUpdatesInterval = 0.1; diff --git a/Source/Engine/Threading/TaskGraph.cpp b/Source/Engine/Threading/TaskGraph.cpp index 10b6ea0c8..c016bfd40 100644 --- a/Source/Engine/Threading/TaskGraph.cpp +++ b/Source/Engine/Threading/TaskGraph.cpp @@ -4,6 +4,7 @@ #include "JobSystem.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" namespace { @@ -67,6 +68,7 @@ const Array>& TaskGraph::GetSystems() co void TaskGraph::AddSystem(TaskGraphSystem* system) { + PROFILE_MEM(Engine); _systems.Add(system); } @@ -78,6 +80,7 @@ void TaskGraph::RemoveSystem(TaskGraphSystem* system) void TaskGraph::Execute() { PROFILE_CPU(); + PROFILE_MEM(Engine); for (auto system : _systems) system->PreExecute(this); diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index bc7b88647..951da2316 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -13,6 +13,8 @@ #include "Engine/Render2D/FontManager.h" #include "Engine/Render2D/FontTextureAtlas.h" #include "Engine/Renderer/RenderList.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Content/Assets/MaterialInstance.h" #include "Engine/Content/Content.h" @@ -120,6 +122,9 @@ void TextRender::SetLayoutOptions(TextLayoutOptions& value) void TextRender::UpdateLayout() { + PROFILE_CPU(); + PROFILE_MEM(UI); + // Clear _ib.Clear(); _vb.Clear(); diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index 107a9eded..bf87fd676 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -6,6 +6,7 @@ #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Profiler/ProfilerMemory.h" #if COMPILE_WITHOUT_CSHARP #define UICANVAS_INVOKE(event) @@ -26,6 +27,7 @@ MMethod* UICanvas_ParentChanged = nullptr; auto* managed = GetManagedInstance(); \ if (managed) \ { \ + PROFILE_MEM(UI); \ MObject* exception = nullptr; \ UICanvas_##event->Invoke(managed, nullptr, &exception); \ if (exception) \ @@ -77,6 +79,7 @@ void UICanvas::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_GET_OTHER_OBJ(UICanvas); #if !COMPILE_WITHOUT_CSHARP + PROFILE_MEM(UI); stream.JKEY("V"); void* params[1]; params[0] = other ? other->GetOrCreateManagedInstance() : nullptr; @@ -109,6 +112,7 @@ void UICanvas::Deserialize(DeserializeStream& stream, ISerializeModifier* modifi const auto dataMember = stream.FindMember("V"); if (dataMember != stream.MemberEnd()) { + PROFILE_MEM(UI); rapidjson_flax::StringBuffer buffer; rapidjson_flax::Writer writer(buffer); dataMember->value.Accept(writer); diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index 8321c33fc..06554542b 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -7,6 +7,7 @@ #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Profiler/ProfilerMemory.h" #if COMPILE_WITHOUT_CSHARP #define UICONTROL_INVOKE(event) @@ -25,6 +26,7 @@ MMethod* UIControl_EndPlay = nullptr; auto* managed = GetManagedInstance(); \ if (managed) \ { \ + PROFILE_MEM(UI); \ MObject* exception = nullptr; \ UIControl_##event->Invoke(managed, nullptr, &exception); \ if (exception) \ @@ -78,6 +80,7 @@ void UIControl::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(NavTargetRight, _navTargetRight); #if !COMPILE_WITHOUT_CSHARP + PROFILE_MEM(UI); void* params[2]; MString* controlType = nullptr; params[0] = &controlType; @@ -129,6 +132,7 @@ void UIControl::Deserialize(DeserializeStream& stream, ISerializeModifier* modif DESERIALIZE_MEMBER(NavTargetRight, _navTargetRight); #if !COMPILE_WITHOUT_CSHARP + PROFILE_MEM(UI); MTypeObject* typeObj = nullptr; const auto controlMember = stream.FindMember("Control"); if (controlMember != stream.MemberEnd()) diff --git a/Source/Engine/Video/AV/VideoBackendAV.cpp b/Source/Engine/Video/AV/VideoBackendAV.cpp index 2d73144c9..c9563d9e2 100644 --- a/Source/Engine/Video/AV/VideoBackendAV.cpp +++ b/Source/Engine/Video/AV/VideoBackendAV.cpp @@ -5,6 +5,7 @@ #include "VideoBackendAV.h" #include "Engine/Platform/Apple/AppleUtils.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/TaskGraph.h" #include "Engine/Core/Log.h" #include "Engine/Engine/Globals.h" @@ -39,6 +40,7 @@ namespace AV void UpdatePlayer(int32 index) { PROFILE_CPU(); + PROFILE_MEM(Video); auto& player = *Players[index]; ZoneText(player.DebugUrl, player.DebugUrlLen); auto& playerAV = player.GetBackendState(); @@ -152,6 +154,7 @@ namespace AV bool VideoBackendAV::Player_Create(const VideoBackendPlayerInfo& info, VideoBackendPlayer& player) { PROFILE_CPU(); + PROFILE_MEM(Video); player = VideoBackendPlayer(); auto& playerAV = player.GetBackendState(); @@ -210,6 +213,7 @@ void VideoBackendAV::Player_Destroy(VideoBackendPlayer& player) void VideoBackendAV::Player_UpdateInfo(VideoBackendPlayer& player, const VideoBackendPlayerInfo& info) { PROFILE_CPU(); + PROFILE_MEM(Video); auto& playerAV = player.GetBackendState(); playerAV.Player.actionAtItemEnd = info.Loop ? AVPlayerActionAtItemEndNone : AVPlayerActionAtItemEndPause; // TODO: spatial audio diff --git a/Source/Engine/Video/MF/VideoBackendMF.cpp b/Source/Engine/Video/MF/VideoBackendMF.cpp index c6af1cef8..01d6ec481 100644 --- a/Source/Engine/Video/MF/VideoBackendMF.cpp +++ b/Source/Engine/Video/MF/VideoBackendMF.cpp @@ -4,6 +4,7 @@ #include "VideoBackendMF.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/TaskGraph.h" #include "Engine/Core/Log.h" #include "Engine/Engine/Time.h" @@ -43,6 +44,7 @@ namespace MF bool Configure(VideoBackendPlayer& player, VideoPlayerMF& playerMF, DWORD streamIndex) { PROFILE_CPU_NAMED("Configure"); + PROFILE_MEM(Video); IMFMediaType *mediaType = nullptr, *nativeType = nullptr; bool result = true; @@ -367,6 +369,7 @@ namespace MF void UpdatePlayer(int32 index) { PROFILE_CPU(); + PROFILE_MEM(Video); auto& player = *Players[index]; ZoneText(player.DebugUrl, player.DebugUrlLen); auto& playerMF = player.GetBackendState(); @@ -453,6 +456,7 @@ namespace MF bool VideoBackendMF::Player_Create(const VideoBackendPlayerInfo& info, VideoBackendPlayer& player) { PROFILE_CPU(); + PROFILE_MEM(Video); player = VideoBackendPlayer(); auto& playerMF = player.GetBackendState(); @@ -572,6 +576,7 @@ const Char* VideoBackendMF::Base_Name() bool VideoBackendMF::Base_Init() { PROFILE_CPU(); + PROFILE_MEM(Video); // Init COM HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); diff --git a/Source/Engine/Video/Video.cpp b/Source/Engine/Video/Video.cpp index 2e76d8960..6b44102d9 100644 --- a/Source/Engine/Video/Video.cpp +++ b/Source/Engine/Video/Video.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Math/Quaternion.h" #include "Engine/Core/Math/Transform.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Graphics/GPUDevice.h" @@ -70,6 +71,7 @@ protected: if (!frame->IsAllocated()) return Result::MissingResources; PROFILE_CPU(); + PROFILE_MEM(Video); ZoneText(_player->DebugUrl, _player->DebugUrlLen); if (PixelFormatExtensions::IsVideo(_player->Format)) @@ -159,6 +161,7 @@ public: void InitBackend(int32 index, VideoBackend* backend) { + PROFILE_MEM(Video); LOG(Info, "Video initialization... (backend: {0})", backend->Base_Name()); if (backend->Base_Init()) { @@ -177,6 +180,7 @@ TaskGraphSystem* Video::System = nullptr; void VideoSystem::Execute(TaskGraph* graph) { PROFILE_CPU_NAMED("Video.Update"); + PROFILE_MEM(Video); // Update backends for (VideoBackend*& backend : VideoServiceInstance.Backends) @@ -309,6 +313,7 @@ void VideoBackendPlayer::InitVideoFrame() void VideoBackendPlayer::UpdateVideoFrame(Span data, TimeSpan time, TimeSpan duration) { PROFILE_CPU(); + PROFILE_MEM(Video); ZoneText(DebugUrl, DebugUrlLen); VideoFrameTime = time; VideoFrameDuration = duration; @@ -356,6 +361,7 @@ void VideoBackendPlayer::UpdateVideoFrame(Span data, TimeSpan time, TimeSp void VideoBackendPlayer::UpdateAudioBuffer(Span data, TimeSpan time, TimeSpan duration) { PROFILE_CPU(); + PROFILE_MEM(Video); ZoneText(DebugUrl, DebugUrlLen); AudioBufferTime = time; AudioBufferDuration = duration; From c1b1f4afc45bde3520e591821254155d0137017a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 22 May 2025 04:49:48 +0200 Subject: [PATCH 06/84] Add process memory stats for Apple platforms --- Source/Engine/Platform/Apple/ApplePlatform.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 5104dd5f8..ad86ed7da 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -224,8 +224,11 @@ MemoryStats ApplePlatform::GetMemoryStats() ProcessMemoryStats ApplePlatform::GetProcessMemoryStats() { ProcessMemoryStats result; - result.UsedPhysicalMemory = 1024; - result.UsedVirtualMemory = 1024; + mach_task_basic_info_data_t taskInfo; + mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; + task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&taskInfo, &count); + result.UsedPhysicalMemory = taskInfo.resident_size; + result.UsedVirtualMemory = result.UsedPhysicalMemory; return result; } From d24f9d1e1e5346b0ec088271b1408a3b8bb1c9df Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 22 May 2025 05:18:56 +0200 Subject: [PATCH 07/84] Add warning when using memory profiler without enabled on startup --- Source/Editor/Windows/Profiler/Memory.cs | 15 +++++++++++++++ Source/Engine/Profiler/ProfilerMemory.cpp | 4 ++++ Source/Engine/Profiler/ProfilerMemory.h | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index f33bec4cc..806bed18f 100644 --- a/Source/Editor/Windows/Profiler/Memory.cs +++ b/Source/Editor/Windows/Profiler/Memory.cs @@ -29,6 +29,7 @@ namespace FlaxEditor.Windows.Profiler private List _tableRowsCache; private string[] _groupNames; private int[] _groupOrder; + private Label _warningText; public Memory() : base("Memory") @@ -65,6 +66,18 @@ namespace FlaxEditor.Windows.Profiler }; _managedAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged; + // Warning text + if (!ProfilerMemory.Enabled) + { + _warningText = new Label + { + Text = "Detailed memory profiling is disabled. Run with command line: -mem", + TextColor = Color.Red, + Visible = false, + Parent = layout, + }; + } + // Table var style = Style.Current; var headerColor = style.LightBackground; @@ -204,6 +217,8 @@ namespace FlaxEditor.Windows.Profiler { if (_frames.Count == 0) return; + if (_warningText != null) + _warningText.Visible = true; var frame = _frames.Get(selectedFrame); var totalUage = frame.Usage.Values0[(int)ProfilerMemory.Groups.TotalTracked]; var totalPeek = frame.Peek.Values0[(int)ProfilerMemory.Groups.TotalTracked]; diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index e617d712c..adb244f26 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -186,6 +186,10 @@ namespace output.AppendLine(); } #endif + + // Warn that data might be missing due to inactive profiler + if (!ProfilerMemory::Enabled) + output.AppendLine(TEXT("Detailed memory profiling is disabled. Run with command line: -mem")); } FORCE_INLINE void AddGroupMemory(ProfilerMemory::Groups group, int64 add) diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h index 65ed5d9ab..206814560 100644 --- a/Source/Engine/Profiler/ProfilerMemory.h +++ b/Source/Engine/Profiler/ProfilerMemory.h @@ -215,7 +215,7 @@ public: /// /// The profiling tools usage flag. Can be used to disable profiler. Run engine with '-mem' command line to activate it from start. /// - static bool Enabled; + API_FIELD(ReadOnly) static bool Enabled; static void OnMemoryAlloc(void* ptr, uint64 size); static void OnMemoryFree(void* ptr); From bb855e2663a935ce7170c1554f293dfa2332067e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 22 May 2025 05:34:36 +0200 Subject: [PATCH 08/84] Add suport for Tracy profiler on Mac --- Source/Engine/Profiler/Profiler.Build.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Profiler/Profiler.Build.cs b/Source/Engine/Profiler/Profiler.Build.cs index 72ca2a9e4..f47cbcf87 100644 --- a/Source/Engine/Profiler/Profiler.Build.cs +++ b/Source/Engine/Profiler/Profiler.Build.cs @@ -35,6 +35,7 @@ public class Profiler : EngineModule case TargetPlatform.Linux: case TargetPlatform.Windows: case TargetPlatform.Switch: + case TargetPlatform.Mac: options.PublicDependencies.Add("tracy"); break; } From f9cb4ddae24cf10e9fe7b6b71e4869395eb0a9ab Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 24 May 2025 05:08:32 +0200 Subject: [PATCH 09/84] Add new Arena Allocator for optimized dynamic memory allocations with a shared lifetime --- Source/Engine/Core/Memory/Allocation.cpp | 44 +++++++++++++++++ Source/Engine/Core/Memory/ArenaAllocation.h | 55 +++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 Source/Engine/Core/Memory/Allocation.cpp create mode 100644 Source/Engine/Core/Memory/ArenaAllocation.h diff --git a/Source/Engine/Core/Memory/Allocation.cpp b/Source/Engine/Core/Memory/Allocation.cpp new file mode 100644 index 000000000..87c9dbc63 --- /dev/null +++ b/Source/Engine/Core/Memory/Allocation.cpp @@ -0,0 +1,44 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#include "ArenaAllocation.h" +#include "../Math/Math.h" + +void ArenaAllocator::Free() +{ + // Free all pages + Page* page = _first; + while (page) + { + Allocator::Free(page->Memory); + Page* next = page->Next; + Allocator::Free(page); + page = next; + } +} + +void* ArenaAllocator::Allocate(uint64 size, uint64 alignment) +{ + // Find the first page that has some space left + Page* page = _first; + while (page && page->Offset + size + alignment > page->Size) + page = page->Next; + + // Create a new page if need to + if (!page) + { + uint64 pageSize = Math::Max(_pageSize, size); + page = (Page*)Allocator::Allocate(sizeof(Page)); + page->Memory = Allocator::Allocate(pageSize); + page->Next = _first; + page->Offset = 0; + page->Size = pageSize; + _first = page; + } + + // Allocate within a page + page->Offset = Math::AlignUp(page->Offset, (uint32)alignment); + void* mem = (byte*)page->Memory + page->Offset; + page->Offset += size; + + return mem; +} \ No newline at end of file diff --git a/Source/Engine/Core/Memory/ArenaAllocation.h b/Source/Engine/Core/Memory/ArenaAllocation.h new file mode 100644 index 000000000..18915853d --- /dev/null +++ b/Source/Engine/Core/Memory/ArenaAllocation.h @@ -0,0 +1,55 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#include "Allocation.h" + +/// +/// Allocator that uses pages for stack-based allocs without freeing memory during it's lifetime. +/// +class ArenaAllocator +{ +private: + struct Page + { + void* Memory; + Page* Next; + uint32 Offset, Size; + }; + + int32 _pageSize; + Page* _first = nullptr; + +public: + ArenaAllocator(int32 pageSizeBytes = 1024 * 1024) // 1 MB by default + : _pageSize(pageSizeBytes) + { + } + + ~ArenaAllocator() + { + Free(); + } + + // Allocates a chunk of unitialized memory. + void* Allocate(uint64 size, uint64 alignment = 1); + + // Frees all memory allocations within allocator. + void Free(); + + // Creates a new object within the arena allocator. + template + inline T* New(Args&&...args) + { + T* ptr = (T*)Allocate(sizeof(T)); + new(ptr) T(Forward(args)...); + return ptr; + } + + // Invokes destructor on values in a dictionary and clears it. + template + void ClearDelete(Dictionary& collection) + { + for (auto it = collection.Begin(); it.IsNotEnd(); ++it) + Memory::DestructItem(it->Value); + collection.Clear(); + } +}; From 410ec0465ce109af0d36bed89209505546e7c350 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 25 May 2025 02:04:16 +0200 Subject: [PATCH 10/84] Optimize CSharp scripting runtime to use arena allocator per-assembly --- Source/Editor/Scripting/ScriptsBuilder.cpp | 4 +- Source/Engine/Core/Memory/ArenaAllocation.h | 12 ++- Source/Engine/Core/Types/Variant.cpp | 2 +- Source/Engine/Debug/DebugCommands.cpp | 2 +- Source/Engine/Scripting/BinaryModule.cpp | 2 +- .../Engine/Scripting/ManagedCLR/MAssembly.h | 12 ++- Source/Engine/Scripting/ManagedCLR/MClass.h | 30 +++--- Source/Engine/Scripting/ManagedCLR/MCore.cpp | 15 +++ Source/Engine/Scripting/ManagedCLR/MEvent.h | 6 +- Source/Engine/Scripting/ManagedCLR/MField.h | 5 +- Source/Engine/Scripting/ManagedCLR/MMethod.h | 8 +- .../Engine/Scripting/ManagedCLR/MProperty.h | 6 +- Source/Engine/Scripting/ManagedCLR/MUtils.cpp | 6 +- Source/Engine/Scripting/ManagedCLR/MUtils.h | 2 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 92 ++++++++++--------- 15 files changed, 126 insertions(+), 78 deletions(-) diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 84df897e6..ea58f3240 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -78,7 +78,7 @@ namespace ScriptsBuilderImpl void onScriptsReloadEnd(); void onScriptsLoaded(); - void GetClassName(const StringAnsi& fullname, StringAnsi& className); + void GetClassName(const StringAnsiView fullname, StringAnsi& className); void onCodeEditorAsyncOpenBegin() { @@ -273,7 +273,7 @@ bool ScriptsBuilder::GenerateProject(const StringView& customArgs) return RunBuildTool(args); } -void ScriptsBuilderImpl::GetClassName(const StringAnsi& fullname, StringAnsi& className) +void ScriptsBuilderImpl::GetClassName(const StringAnsiView fullname, StringAnsi& className) { const auto lastDotIndex = fullname.FindLast('.'); if (lastDotIndex != -1) diff --git a/Source/Engine/Core/Memory/ArenaAllocation.h b/Source/Engine/Core/Memory/ArenaAllocation.h index 18915853d..43004ab30 100644 --- a/Source/Engine/Core/Memory/ArenaAllocation.h +++ b/Source/Engine/Core/Memory/ArenaAllocation.h @@ -44,9 +44,19 @@ public: return ptr; } + // Invokes destructor on values in an array and clears it. + template + static void ClearDelete(Array& collection) + { + Value* ptr = collection.Get(); + for (int32 i = 0; i < collection.Count(); i++) + Memory::DestructItem(ptr[i]); + collection.Clear(); + } + // Invokes destructor on values in a dictionary and clears it. template - void ClearDelete(Dictionary& collection) + static void ClearDelete(Dictionary& collection) { for (auto it = collection.Begin(); it.IsNotEnd(); ++it) Memory::DestructItem(it->Value); diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index f45856443..873aba0eb 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -118,7 +118,7 @@ VariantType::VariantType(Types type, const MClass* klass) #if USE_CSHARP if (klass) { - const StringAnsi& typeName = klass->GetFullName(); + const StringAnsiView typeName = klass->GetFullName(); const int32 length = typeName.Length(); TypeName = static_cast(Allocator::Allocate(length + 1)); Platform::MemoryCopy(TypeName, typeName.Get(), length); diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index fcea938aa..6927a1da9 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -215,7 +215,7 @@ namespace { if (!method->IsStatic()) continue; - const StringAnsi& name = method->GetName(); + const StringAnsiView name = method->GetName(); if (name.Contains("Internal_") || mclass->GetFullName().Contains(".Interop.")) continue; diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 3030b041d..1a48a3846 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1029,7 +1029,7 @@ void ManagedBinaryModule::InitType(MClass* mclass) { #if !COMPILE_WITHOUT_CSHARP // Skip if already initialized - const StringAnsi& typeName = mclass->GetFullName(); + const StringAnsiView typeName = mclass->GetFullName(); if (TypeNameToTypeIndex.ContainsKey(typeName)) return; PROFILE_MEM(Scripting); diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h index 2e5af191a..6c0aa9579 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h @@ -7,6 +7,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Memory/ArenaAllocation.h" #include "Engine/Platform/CriticalSection.h" /// @@ -19,7 +20,7 @@ class FLAXENGINE_API MAssembly friend Scripting; public: - typedef Dictionary ClassesDictionary; + typedef Dictionary ClassesDictionary; private: #if USE_MONO @@ -67,6 +68,15 @@ public: /// ~MAssembly(); +public: + /// + /// Memory storage with all assembly-related data that shares its lifetime (eg. metadata). + /// + ArenaAllocator Memory; + + // Allocates the given string within a memory that has lifetime of assembly. + StringAnsiView AllocString(const char* str); + public: /// /// Managed assembly actions delegate type. diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.h b/Source/Engine/Scripting/ManagedCLR/MClass.h index b44c446cb..a74b4f7da 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.h +++ b/Source/Engine/Scripting/ManagedCLR/MClass.h @@ -17,14 +17,13 @@ private: mutable void* _attrInfo = nullptr; #elif USE_NETCORE void* _handle; - StringAnsi _name; - StringAnsi _namespace; + StringAnsiView _name; + StringAnsiView _namespace; + StringAnsiView _fullname; uint32 _types = 0; mutable uint32 _size = 0; #endif - const MAssembly* _assembly; - - StringAnsi _fullname; + MAssembly* _assembly; mutable Array _methods; mutable Array _fields; @@ -47,12 +46,13 @@ private: int32 _isInterface : 1; int32 _isValueType : 1; int32 _isEnum : 1; + int32 _isGeneric : 1; public: #if USE_MONO MClass(const MAssembly* parentAssembly, MonoClass* monoClass, const StringAnsi& fullname); #elif USE_NETCORE - MClass(const MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes typeAttributes); + MClass(MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes typeAttributes); #endif /// @@ -64,7 +64,7 @@ public: /// /// Gets the parent assembly. /// - const MAssembly* GetAssembly() const + FORCE_INLINE MAssembly* GetAssembly() const { return _assembly; } @@ -72,7 +72,7 @@ public: /// /// Gets the full name of the class (namespace and typename). /// - FORCE_INLINE const StringAnsi& GetFullName() const + FORCE_INLINE StringAnsiView GetFullName() const { return _fullname; } @@ -80,12 +80,18 @@ public: /// /// Gets the name of the class. /// - StringAnsiView GetName() const; + FORCE_INLINE StringAnsiView GetName() const + { + return _name; + } /// /// Gets the namespace of the class. /// - StringAnsiView GetNamespace() const; + FORCE_INLINE StringAnsiView GetNamespace() const + { + return _name; + } #if USE_MONO /// @@ -161,9 +167,9 @@ public: /// /// Gets if class is generic /// - bool IsGeneric() const + FORCE_INLINE bool IsGeneric() const { - return _fullname.FindLast('`') != -1; + return _isGeneric != 0; } /// diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 8761c309c..4ded56b52 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -71,6 +71,17 @@ MAssembly::~MAssembly() Unload(); } +StringAnsiView MAssembly::AllocString(const char* str) +{ + if (!str) + return StringAnsiView::Empty; + int32 len = StringUtils::Length(str); + char* mem = (char*)Memory.Allocate(len + 1); + Platform::MemoryCopy(mem, str, len); + mem[len] = 0; + return StringAnsiView(mem, len); +} + String MAssembly::ToString() const { return _name.ToString(); @@ -127,7 +138,11 @@ void MAssembly::Unload(bool isReloading) _isLoading = false; _isLoaded = false; _hasCachedClasses = false; +#if USE_NETCORE + ArenaAllocator::ClearDelete(_classes); +#else _classes.ClearDelete(); +#endif Unloaded(this); } diff --git a/Source/Engine/Scripting/ManagedCLR/MEvent.h b/Source/Engine/Scripting/ManagedCLR/MEvent.h index 52573e204..0aade183e 100644 --- a/Source/Engine/Scripting/ManagedCLR/MEvent.h +++ b/Source/Engine/Scripting/ManagedCLR/MEvent.h @@ -15,16 +15,16 @@ class FLAXENGINE_API MEvent protected: #if USE_MONO MonoEvent* _monoEvent; + StringAnsi _name; #elif USE_NETCORE void* _handle; + StringAnsiView _name; #endif mutable MMethod* _addMethod; mutable MMethod* _removeMethod; MClass* _parentClass; - StringAnsi _name; - mutable int32 _hasCachedAttributes : 1; mutable int32 _hasAddMonoMethod : 1; mutable int32 _hasRemoveMonoMethod : 1; @@ -42,7 +42,7 @@ public: /// /// Gets the event name. /// - FORCE_INLINE const StringAnsi& GetName() const + FORCE_INLINE StringAnsiView GetName() const { return _name; } diff --git a/Source/Engine/Scripting/ManagedCLR/MField.h b/Source/Engine/Scripting/ManagedCLR/MField.h index 49bce9f54..ed73db711 100644 --- a/Source/Engine/Scripting/ManagedCLR/MField.h +++ b/Source/Engine/Scripting/ManagedCLR/MField.h @@ -17,14 +17,15 @@ protected: #if USE_MONO MonoClassField* _monoField; MonoType* _monoType; + StringAnsi _name; #elif USE_NETCORE void* _handle; void* _type; int32 _fieldOffset; + StringAnsiView _name; #endif MClass* _parentClass; - StringAnsi _name; MVisibility _visibility; @@ -44,7 +45,7 @@ public: /// /// Gets field name. /// - FORCE_INLINE const StringAnsi& GetName() const + FORCE_INLINE StringAnsiView GetName() const { return _name; } diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h index 989147754..8d24b533b 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.h +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h @@ -21,15 +21,16 @@ class FLAXENGINE_API MMethod protected: #if USE_MONO MonoMethod* _monoMethod; + StringAnsi _name; #elif USE_NETCORE void* _handle; + StringAnsiView _name; int32 _paramsCount; mutable void* _returnType; mutable Array> _parameterTypes; void CacheSignature() const; #endif MClass* _parentClass; - StringAnsi _name; MVisibility _visibility; #if !USE_MONO_AOT void* _cachedThunk = nullptr; @@ -48,12 +49,11 @@ public: explicit MMethod(MonoMethod* monoMethod, MClass* parentClass); explicit MMethod(MonoMethod* monoMethod, const char* name, MClass* parentClass); #elif USE_NETCORE - MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 paramsCount, MMethodAttributes attributes); + MMethod(MClass* parentClass, StringAnsiView name, void* handle, int32 paramsCount, MMethodAttributes attributes); #endif public: #if COMPILE_WITH_PROFILER - StringAnsi ProfilerName; SourceLocationData ProfilerData; #endif @@ -109,7 +109,7 @@ public: /// /// Gets the method name. /// - FORCE_INLINE const StringAnsi& GetName() const + FORCE_INLINE StringAnsiView GetName() const { return _name; } diff --git a/Source/Engine/Scripting/ManagedCLR/MProperty.h b/Source/Engine/Scripting/ManagedCLR/MProperty.h index 3be94eb1a..7e426b474 100644 --- a/Source/Engine/Scripting/ManagedCLR/MProperty.h +++ b/Source/Engine/Scripting/ManagedCLR/MProperty.h @@ -17,16 +17,16 @@ class FLAXENGINE_API MProperty protected: #if USE_MONO MonoProperty* _monoProperty; + StringAnsi _name; #elif USE_NETCORE void* _handle; + StringAnsiView _name; #endif mutable MMethod* _getMethod; mutable MMethod* _setMethod; MClass* _parentClass; - StringAnsi _name; - mutable int32 _hasCachedAttributes : 1; mutable int32 _hasSetMethod : 1; mutable int32 _hasGetMethod : 1; @@ -49,7 +49,7 @@ public: /// /// Gets the property name. /// - FORCE_INLINE const StringAnsi& GetName() const + FORCE_INLINE StringAnsiView GetName() const { return _name; } diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index a52d55f67..fd0596ef5 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -150,7 +150,7 @@ ScriptingTypeHandle MUtils::UnboxScriptingTypeHandle(MTypeObject* value) MClass* klass = GetClass(value); if (!klass) return ScriptingTypeHandle(); - const StringAnsi& typeName = klass->GetFullName(); + const StringAnsiView typeName = klass->GetFullName(); const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName); if (!typeHandle) LOG(Warning, "Unknown scripting type {}", String(typeName)); @@ -821,14 +821,14 @@ MObject* MUtils::BoxVariant(const Variant& value) } } -const StringAnsi& MUtils::GetClassFullname(MObject* obj) +StringAnsiView MUtils::GetClassFullname(MObject* obj) { if (obj) { MClass* mClass = MCore::Object::GetClass(obj); return mClass->GetFullName(); } - return StringAnsi::Empty; + return StringAnsiView::Empty; } MClass* MUtils::GetClass(MTypeObject* type) diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index f3bc27172..f642681d2 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -400,7 +400,7 @@ struct MConverter> namespace MUtils { // Outputs the full typename for the type of the specified object. - extern FLAXENGINE_API const StringAnsi& GetClassFullname(MObject* obj); + extern FLAXENGINE_API StringAnsiView GetClassFullname(MObject* obj); // Returns the class of the provided object. extern FLAXENGINE_API MClass* GetClass(MObject* object); diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index d56c0e5ea..cd4da3f20 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -183,7 +183,7 @@ Dictionary CachedAssemblyHandles; /// /// Returns the function pointer to the managed static method in NativeInterop class. /// -void* GetStaticMethodPointer(const String& methodName); +void* GetStaticMethodPointer(StringView methodName); /// /// Calls the managed static method with given parameters. @@ -753,12 +753,13 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const static void* GetManagedClassesPtr = GetStaticMethodPointer(TEXT("GetManagedClasses")); CallStaticMethod(GetManagedClassesPtr, _handle, &managedClasses, &classCount); _classes.EnsureCapacity(classCount); + MAssembly* assembly = const_cast(this); for (int32 i = 0; i < classCount; i++) { NativeClassDefinitions& managedClass = managedClasses[i]; // Create class object - MClass* klass = New(this, managedClass.typeHandle, managedClass.name, managedClass.fullname, managedClass.namespace_, managedClass.typeAttributes); + MClass* klass = assembly->Memory.New(assembly, managedClass.typeHandle, managedClass.name, managedClass.fullname, managedClass.namespace_, managedClass.typeAttributes); _classes.Add(klass->GetFullName(), klass); managedClass.nativePointer = klass; @@ -811,7 +812,7 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man CachedAssemblyHandles.Add(assemblyHandle, assembly); } - MClass* klass = New(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes); + MClass* klass = assembly->Memory.New(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes); if (assembly != nullptr) { auto& classes = const_cast(assembly->GetClasses()); @@ -819,7 +820,7 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man if (classes.TryGet(klass->GetFullName(), oldKlass)) { LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName())); - Delete(klass); + Memory::DestructItem(klass); klass = oldKlass; } else @@ -915,12 +916,12 @@ bool MAssembly::UnloadImage(bool isReloading) return false; } -MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes attributes) +MClass::MClass(MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes attributes) : _handle(handle) - , _name(name) - , _namespace(namespace_) + , _name(parentAssembly->AllocString(name)) + , _namespace(parentAssembly->AllocString(namespace_)) + , _fullname(parentAssembly->AllocString(fullname)) , _assembly(parentAssembly) - , _fullname(fullname) , _hasCachedProperties(false) , _hasCachedFields(false) , _hasCachedMethods(false) @@ -967,6 +968,8 @@ MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name, static void* TypeIsEnumPtr = GetStaticMethodPointer(TEXT("TypeIsEnum")); _isEnum = CallStaticMethod(TypeIsEnumPtr, handle); + _isGeneric = _fullname.FindLast('`') != -1; + CachedClassHandles[handle] = this; } @@ -982,24 +985,14 @@ bool MAssembly::ResolveMissingFile(String& assemblyPath) const MClass::~MClass() { - _methods.ClearDelete(); - _fields.ClearDelete(); - _properties.ClearDelete(); - _events.ClearDelete(); + ArenaAllocator::ClearDelete(_methods); + ArenaAllocator::ClearDelete(_fields); + ArenaAllocator::ClearDelete(_properties); + ArenaAllocator::ClearDelete(_events); CachedClassHandles.Remove(_handle); } -StringAnsiView MClass::GetName() const -{ - return _name; -} - -StringAnsiView MClass::GetNamespace() const -{ - return _namespace; -} - MType* MClass::GetType() const { return (MType*)_handle; @@ -1071,10 +1064,11 @@ const Array& MClass::GetMethods() const static void* GetClassMethodsPtr = GetStaticMethodPointer(TEXT("GetClassMethods")); CallStaticMethod(GetClassMethodsPtr, _handle, &methods, &methodsCount); _methods.Resize(methodsCount); + MAssembly* assembly = const_cast(_assembly); for (int32 i = 0; i < methodsCount; i++) { NativeMethodDefinitions& definition = methods[i]; - MMethod* method = New(const_cast(this), StringAnsi(definition.name), definition.handle, definition.numParameters, definition.methodAttributes); + MMethod* method = assembly->Memory.New(const_cast(this), assembly->AllocString(definition.name), definition.handle, definition.numParameters, definition.methodAttributes); _methods[i] = method; MCore::GC::FreeMemory((void*)definition.name); } @@ -1112,7 +1106,7 @@ const Array& MClass::GetFields() const for (int32 i = 0; i < numFields; i++) { NativeFieldDefinitions& definition = fields[i]; - MField* field = New(const_cast(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes); + MField* field = _assembly->Memory.New(const_cast(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes); _fields[i] = field; MCore::GC::FreeMemory((void*)definition.name); } @@ -1162,7 +1156,7 @@ const Array& MClass::GetProperties() const for (int i = 0; i < numProperties; i++) { const NativePropertyDefinitions& definition = foundProperties[i]; - MProperty* property = New(const_cast(this), definition.name, definition.propertyHandle, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes); + MProperty* property = _assembly->Memory.New(const_cast(this), definition.name, definition.propertyHandle, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes); _properties[i] = property; MCore::GC::FreeMemory((void*)definition.name); } @@ -1241,7 +1235,7 @@ MEvent::MEvent(MClass* parentClass, void* handle, const char* name) , _addMethod(nullptr) , _removeMethod(nullptr) , _parentClass(parentClass) - , _name(name) + , _name(parentClass->GetAssembly()->AllocString(name)) , _hasCachedAttributes(false) , _hasAddMonoMethod(true) , _hasRemoveMonoMethod(true) @@ -1317,7 +1311,7 @@ MField::MField(MClass* parentClass, void* handle, const char* name, void* type, , _type(type) , _fieldOffset(fieldOffset) , _parentClass(parentClass) - , _name(name) + , _name(parentClass->GetAssembly()->AllocString(name)) , _hasCachedAttributes(false) { switch (attributes & MFieldAttributes::FieldAccessMask) @@ -1409,11 +1403,11 @@ const Array& MField::GetAttributes() const return _attributes; } -MMethod::MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 paramsCount, MMethodAttributes attributes) +MMethod::MMethod(MClass* parentClass, StringAnsiView name, void* handle, int32 paramsCount, MMethodAttributes attributes) : _handle(handle) , _paramsCount(paramsCount) , _parentClass(parentClass) - , _name(MoveTemp(name)) + , _name(name) , _hasCachedAttributes(false) , _hasCachedSignature(false) { @@ -1443,13 +1437,15 @@ MMethod::MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 par _isStatic = (attributes & MMethodAttributes::Static) == MMethodAttributes::Static; #if COMPILE_WITH_PROFILER - const StringAnsi& className = parentClass->GetFullName(); - ProfilerName.Resize(className.Length() + 2 + _name.Length()); - Platform::MemoryCopy(ProfilerName.Get(), className.Get(), className.Length()); - ProfilerName.Get()[className.Length()] = ':'; - ProfilerName.Get()[className.Length() + 1] = ':'; - Platform::MemoryCopy(ProfilerName.Get() + className.Length() + 2, _name.Get(), _name.Length()); - ProfilerData.name = ProfilerName.Get(); + // Setup Tracy profiler entry (use assembly memory) + const StringAnsiView className = parentClass->GetFullName(); + char* profilerName = (char*)parentClass->GetAssembly()->Memory.Allocate(className.Length() + _name.Length() + 3); + Platform::MemoryCopy(profilerName, className.Get(), className.Length()); + profilerName[className.Length()] = ':'; + profilerName[className.Length() + 1] = ':'; + Platform::MemoryCopy(profilerName + className.Length() + 2, _name.Get(), _name.Length()); + profilerName[className.Length() + 2 + _name.Length()] = 0; + ProfilerData.name = profilerName; ProfilerData.function = _name.Get(); ProfilerData.file = nullptr; ProfilerData.line = 0; @@ -1573,20 +1569,30 @@ const Array& MMethod::GetAttributes() const return _attributes; } +FORCE_INLINE StringAnsiView GetPropertyMethodName(MProperty* property, StringAnsiView prefix) +{ + StringAnsiView name = property->GetName(); + char* mem = (char*)property->GetParentClass()->GetAssembly()->Memory.Allocate(name.Length() + prefix.Length() + 1); + Platform::MemoryCopy(mem, prefix.Get(), prefix.Length()); + Platform::MemoryCopy(mem + prefix.Length(), name.Get(), name.Length()); + mem[name.Length() + prefix.Length()] = 0; + return StringAnsiView(mem, name.Length() + prefix.Length() + 1); +} + MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes) : _parentClass(parentClass) - , _name(name) + , _name(parentClass->GetAssembly()->AllocString(name)) , _handle(handle) , _hasCachedAttributes(false) { _hasGetMethod = getterHandle != nullptr; if (_hasGetMethod) - _getMethod = New(parentClass, StringAnsi("get_" + _name), getterHandle, 0, getterAttributes); + _getMethod = parentClass->GetAssembly()->Memory.New(parentClass, GetPropertyMethodName(this, StringAnsiView("get_", 4)), getterHandle, 0, getterAttributes); else _getMethod = nullptr; _hasSetMethod = setterHandle != nullptr; if (_hasSetMethod) - _setMethod = New(parentClass, StringAnsi("set_" + _name), setterHandle, 1, setterAttributes); + _setMethod = parentClass->GetAssembly()->Memory.New(parentClass, GetPropertyMethodName(this, StringAnsiView("set_", 4)), setterHandle, 1, setterAttributes); else _setMethod = nullptr; } @@ -1594,9 +1600,9 @@ MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void* MProperty::~MProperty() { if (_getMethod) - Delete(_getMethod); + Memory::DestructItem(_getMethod); if (_setMethod) - Delete(_setMethod); + Memory::DestructItem(_setMethod); } MMethod* MProperty::GetGetMethod() const @@ -1683,7 +1689,7 @@ MClass* GetOrCreateClass(MType* typeHandle) static void* GetManagedClassFromTypePtr = GetStaticMethodPointer(TEXT("GetManagedClassFromType")); CallStaticMethod(GetManagedClassFromTypePtr, typeHandle, &classInfo, &assemblyHandle); MAssembly* assembly = GetAssembly(assemblyHandle); - klass = New(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes); + klass = assembly->Memory.New(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes); if (assembly != nullptr) { auto& classes = const_cast(assembly->GetClasses()); @@ -1889,7 +1895,7 @@ void ShutdownHostfxr() { } -void* GetStaticMethodPointer(const String& methodName) +void* GetStaticMethodPointer(StringView methodName) { void* fun; if (CachedFunctions.TryGet(methodName, fun)) From 9aaba955d0b8942079f8374582da1b9596af9d17 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 25 May 2025 02:04:56 +0200 Subject: [PATCH 11/84] Fix profiler tables to use column headers aligned to center --- Source/Editor/GUI/ColumnDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/ColumnDefinition.cs b/Source/Editor/GUI/ColumnDefinition.cs index 6c1f8050a..4ddac7802 100644 --- a/Source/Editor/GUI/ColumnDefinition.cs +++ b/Source/Editor/GUI/ColumnDefinition.cs @@ -51,7 +51,7 @@ namespace FlaxEditor.GUI /// /// The column title horizontal text alignment /// - public TextAlignment TitleAlignment = TextAlignment.Near; + public TextAlignment TitleAlignment = TextAlignment.Center; /// /// The column title margin. From 8c62f1120f4e5b8eae05399c85a82070e416d866 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 25 May 2025 17:39:20 +0200 Subject: [PATCH 12/84] Optimize dynamic memory allocations for managed runtime interop collections with a new Arena Allocation --- Source/Engine/Core/Collections/Array.h | 12 +++ Source/Engine/Core/Memory/Allocation.h | 34 ++++++-- Source/Engine/Core/Memory/ArenaAllocation.h | 79 +++++++++++++++++++ .../Engine/Core/Memory/SimpleHeapAllocation.h | 5 ++ Source/Engine/Scripting/BinaryModule.cpp | 4 +- Source/Engine/Scripting/ManagedCLR/MClass.h | 25 +++--- Source/Engine/Scripting/ManagedCLR/MEvent.h | 4 +- Source/Engine/Scripting/ManagedCLR/MField.h | 5 +- Source/Engine/Scripting/ManagedCLR/MMethod.h | 5 +- .../Engine/Scripting/ManagedCLR/MProperty.h | 5 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 34 +++++--- Source/Engine/Scripting/Runtime/Mono.cpp | 10 +-- Source/Engine/Scripting/Runtime/None.cpp | 18 ++--- 13 files changed, 185 insertions(+), 55 deletions(-) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 5845d7f50..ab142961d 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -20,6 +20,7 @@ API_CLASS(InBuild) class Array public: using ItemType = T; using AllocationData = typename AllocationType::template Data; + using AllocationTag = typename AllocationType::Tag; private: int32 _count; @@ -36,6 +37,17 @@ public: { } + /// + /// Initializes an empty without reserving any space. + /// + /// The custom allocation tag. + Array(AllocationTag tag) + : _count(0) + , _capacity(0) + , _allocation(tag) + { + } + /// /// Initializes by reserving space. /// diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h index 6da929d86..d6958d2c3 100644 --- a/Source/Engine/Core/Memory/Allocation.h +++ b/Source/Engine/Core/Memory/Allocation.h @@ -36,6 +36,17 @@ namespace AllocationUtils capacity++; return capacity; } + + inline int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity) + { + if (capacity < minCapacity) + capacity = minCapacity; + if (capacity < 8) + capacity = 8; + else + capacity = RoundUpToPowerOf2(capacity); + return capacity; + } } /// @@ -46,6 +57,7 @@ class FixedAllocation { public: enum { HasSwap = false }; + typedef void* Tag; template class alignas(sizeof(void*)) Data @@ -58,6 +70,10 @@ public: { } + FORCE_INLINE Data(Tag tag) + { + } + FORCE_INLINE ~Data() { } @@ -106,6 +122,7 @@ class HeapAllocation { public: enum { HasSwap = true }; + typedef void* Tag; template class Data @@ -118,6 +135,10 @@ public: { } + FORCE_INLINE Data(Tag tag) + { + } + FORCE_INLINE ~Data() { Allocator::Free(_data); @@ -135,13 +156,7 @@ public: FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, const int32 minCapacity) const { - if (capacity < minCapacity) - capacity = minCapacity; - if (capacity < 8) - capacity = 8; - else - capacity = AllocationUtils::RoundUpToPowerOf2(capacity); - return capacity; + return AllocationUtils::CalculateCapacityGrow(capacity, minCapacity); } FORCE_INLINE void Allocate(const int32 capacity) @@ -184,6 +199,7 @@ class InlinedAllocation { public: enum { HasSwap = false }; + typedef void* Tag; template class alignas(sizeof(void*)) Data @@ -200,6 +216,10 @@ public: { } + FORCE_INLINE Data(Tag tag) + { + } + FORCE_INLINE ~Data() { } diff --git a/Source/Engine/Core/Memory/ArenaAllocation.h b/Source/Engine/Core/Memory/ArenaAllocation.h index 43004ab30..af8df2001 100644 --- a/Source/Engine/Core/Memory/ArenaAllocation.h +++ b/Source/Engine/Core/Memory/ArenaAllocation.h @@ -1,5 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. +#pragma once + #include "Allocation.h" /// @@ -63,3 +65,80 @@ public: collection.Clear(); } }; + +/// +/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op. +/// +class ArenaAllocation +{ +public: + enum { HasSwap = true }; + typedef ArenaAllocator* Tag; + + template + class Data + { + private: + T* _data = nullptr; + ArenaAllocator* _arena = nullptr; + + public: + FORCE_INLINE Data() + { + } + + FORCE_INLINE Data(Tag tag) + { + _arena = tag; + } + + FORCE_INLINE ~Data() + { + } + + FORCE_INLINE T* Get() + { + return _data; + } + + FORCE_INLINE const T* Get() const + { + return _data; + } + + FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, const int32 minCapacity) const + { + return AllocationUtils::CalculateCapacityGrow(capacity, minCapacity); + } + + FORCE_INLINE void Allocate(const int32 capacity) + { + ASSERT_LOW_LAYER(!_data && _arena); + _data = (T*)_arena->Allocate(capacity * sizeof(T), alignof(T)); + } + + FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount) + { + ASSERT_LOW_LAYER(_arena); + T* newData = capacity != 0 ? (T*)_arena->Allocate(capacity * sizeof(T), alignof(T)) : nullptr; + if (oldCount) + { + if (newCount > 0) + Memory::MoveItems(newData, _data, newCount); + Memory::DestructItems(_data, oldCount); + } + _data = newData; + } + + FORCE_INLINE void Free() + { + _data = nullptr; + } + + FORCE_INLINE void Swap(Data& other) + { + ::Swap(_data, other._data); + ::Swap(_arena, other._arena); + } + }; +}; diff --git a/Source/Engine/Core/Memory/SimpleHeapAllocation.h b/Source/Engine/Core/Memory/SimpleHeapAllocation.h index 4df5bb660..6afcffd64 100644 --- a/Source/Engine/Core/Memory/SimpleHeapAllocation.h +++ b/Source/Engine/Core/Memory/SimpleHeapAllocation.h @@ -11,6 +11,7 @@ class SimpleHeapAllocation { public: enum { HasSwap = true }; + typedef void* Tag; template class Data @@ -23,6 +24,10 @@ public: { } + FORCE_INLINE Data(Tag tag) + { + } + FORCE_INLINE ~Data() { if (_data) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 1a48a3846..da01a9f07 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -815,7 +815,7 @@ namespace { MMethod* FindMethod(MClass* mclass, const MMethod* referenceMethod) { - const Array& methods = mclass->GetMethods(); + const auto& methods = mclass->GetMethods(); for (int32 i = 0; i < methods.Count(); i++) { MMethod* method = methods[i]; @@ -1095,7 +1095,7 @@ void ManagedBinaryModule::InitType(MClass* mclass) // Initialize scripting interfaces implemented in C# int32 interfacesCount = 0; MClass* klass = mclass; - const Array& interfaceClasses = klass->GetInterfaces(); + const auto& interfaceClasses = klass->GetInterfaces(); for (const MClass* interfaceClass : interfaceClasses) { const ScriptingTypeHandle interfaceType = FindType(interfaceClass); diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.h b/Source/Engine/Scripting/ManagedCLR/MClass.h index a74b4f7da..a89d75205 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.h +++ b/Source/Engine/Scripting/ManagedCLR/MClass.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Memory/ArenaAllocation.h" #include "MTypes.h" /// @@ -25,12 +26,12 @@ private: #endif MAssembly* _assembly; - mutable Array _methods; - mutable Array _fields; - mutable Array _properties; - mutable Array _attributes; - mutable Array _events; - mutable Array _interfaces; + mutable Array _methods; + mutable Array _fields; + mutable Array _properties; + mutable Array _attributes; + mutable Array _events; + mutable Array _interfaces; MVisibility _visibility; @@ -248,7 +249,7 @@ public: /// /// Be aware this will not include the methods of any base classes. /// The list of methods. - const Array& GetMethods() const; + const Array& GetMethods() const; /// /// Returns an object referencing a field with the specified name. @@ -263,7 +264,7 @@ public: /// /// Be aware this will not include the fields of any base classes. /// The list of fields. - const Array& GetFields() const; + const Array& GetFields() const; /// /// Returns an object referencing a event with the specified name. @@ -276,7 +277,7 @@ public: /// Returns all events belonging to this class. /// /// The list of events. - const Array& GetEvents() const; + const Array& GetEvents() const; /// /// Returns an object referencing a property with the specified name. @@ -291,14 +292,14 @@ public: /// /// Be aware this will not include the properties of any base classes. /// The list of properties. - const Array& GetProperties() const; + const Array& GetProperties() const; /// /// Returns all interfaces implemented by this class (excluding interfaces from base classes). /// /// Be aware this will not include the interfaces of any base classes. /// The list of interfaces. - const Array& GetInterfaces() const; + const Array& GetInterfaces() const; public: /// @@ -332,5 +333,5 @@ public: /// Returns an instance of all attributes connected with given class. Returns null if the class doesn't have any attributes. /// /// The array of attribute objects. - const Array& GetAttributes() const; + const Array& GetAttributes() const; }; diff --git a/Source/Engine/Scripting/ManagedCLR/MEvent.h b/Source/Engine/Scripting/ManagedCLR/MEvent.h index 0aade183e..9d8551774 100644 --- a/Source/Engine/Scripting/ManagedCLR/MEvent.h +++ b/Source/Engine/Scripting/ManagedCLR/MEvent.h @@ -29,7 +29,7 @@ protected: mutable int32 _hasAddMonoMethod : 1; mutable int32 _hasRemoveMonoMethod : 1; - mutable Array _attributes; + mutable Array _attributes; public: #if USE_MONO @@ -121,5 +121,5 @@ public: /// Returns an instance of all attributes connected with given event. Returns null if the event doesn't have any attributes. /// /// The array of attribute objects. - const Array& GetAttributes() const; + const Array& GetAttributes() const; }; diff --git a/Source/Engine/Scripting/ManagedCLR/MField.h b/Source/Engine/Scripting/ManagedCLR/MField.h index ed73db711..66213d6ab 100644 --- a/Source/Engine/Scripting/ManagedCLR/MField.h +++ b/Source/Engine/Scripting/ManagedCLR/MField.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Memory/ArenaAllocation.h" #include "MTypes.h" /// @@ -32,7 +33,7 @@ protected: mutable int32 _hasCachedAttributes : 1; int32 _isStatic : 1; - mutable Array _attributes; + mutable Array _attributes; public: #if USE_MONO @@ -157,5 +158,5 @@ public: /// Returns an instance of all attributes connected with given field. Returns null if the field doesn't have any attributes. /// /// The array of attribute objects. - const Array& GetAttributes() const; + const Array& GetAttributes() const; }; diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h index 8d24b533b..700fa9593 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.h +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Memory/ArenaAllocation.h" #if COMPILE_WITH_PROFILER #include "Engine/Profiler/ProfilerSrcLoc.h" #endif @@ -42,7 +43,7 @@ protected: #endif int32 _isStatic : 1; - mutable Array _attributes; + mutable Array _attributes; public: #if USE_MONO @@ -197,5 +198,5 @@ public: /// Returns an instance of all attributes connected with given method. Returns null if the method doesn't have any attributes. /// /// The array of attribute objects. - const Array& GetAttributes() const; + const Array& GetAttributes() const; }; diff --git a/Source/Engine/Scripting/ManagedCLR/MProperty.h b/Source/Engine/Scripting/ManagedCLR/MProperty.h index 7e426b474..e48d98375 100644 --- a/Source/Engine/Scripting/ManagedCLR/MProperty.h +++ b/Source/Engine/Scripting/ManagedCLR/MProperty.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Memory/ArenaAllocation.h" #include "MTypes.h" /// @@ -31,7 +32,7 @@ protected: mutable int32 _hasSetMethod : 1; mutable int32 _hasGetMethod : 1; - mutable Array _attributes; + mutable Array _attributes; public: #if USE_MONO @@ -135,5 +136,5 @@ public: /// Returns an instance of all attributes connected with given property. Returns null if the property doesn't have any attributes. /// /// The array of attribute objects. - const Array& GetAttributes() const; + const Array& GetAttributes() const; }; diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index cd4da3f20..ce5f315f1 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -212,7 +212,7 @@ MClass* GetClass(MType* typeHandle); MClass* GetOrCreateClass(MType* typeHandle); MType* GetObjectType(MObject* obj); -void* GetCustomAttribute(const Array& attributes, const MClass* attributeClass) +void* GetCustomAttribute(const Array& attributes, const MClass* attributeClass) { for (MObject* attr : attributes) { @@ -223,7 +223,7 @@ void* GetCustomAttribute(const Array& attributes, const MClass* attrib return nullptr; } -void GetCustomAttributes(Array& result, void* handle, void* getAttributesFunc) +void GetCustomAttributes(Array& result, void* handle, void* getAttributesFunc) { MObject** attributes; int numAttributes; @@ -922,6 +922,12 @@ MClass::MClass(MAssembly* parentAssembly, void* handle, const char* name, const , _namespace(parentAssembly->AllocString(namespace_)) , _fullname(parentAssembly->AllocString(fullname)) , _assembly(parentAssembly) + , _methods(&parentAssembly->Memory) + , _fields(&parentAssembly->Memory) + , _properties(&parentAssembly->Memory) + , _attributes(&parentAssembly->Memory) + , _events(&parentAssembly->Memory) + , _interfaces(&parentAssembly->Memory) , _hasCachedProperties(false) , _hasCachedFields(false) , _hasCachedMethods(false) @@ -1050,7 +1056,7 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const return nullptr; } -const Array& MClass::GetMethods() const +const Array& MClass::GetMethods() const { if (_hasCachedMethods) return _methods; @@ -1089,7 +1095,7 @@ MField* MClass::GetField(const char* name) const return nullptr; } -const Array& MClass::GetFields() const +const Array& MClass::GetFields() const { if (_hasCachedFields) return _fields; @@ -1116,7 +1122,7 @@ const Array& MClass::GetFields() const return _fields; } -const Array& MClass::GetEvents() const +const Array& MClass::GetEvents() const { if (_hasCachedEvents) return _events; @@ -1139,7 +1145,7 @@ MProperty* MClass::GetProperty(const char* name) const return nullptr; } -const Array& MClass::GetProperties() const +const Array& MClass::GetProperties() const { if (_hasCachedProperties) return _properties; @@ -1166,7 +1172,7 @@ const Array& MClass::GetProperties() const return _properties; } -const Array& MClass::GetInterfaces() const +const Array& MClass::GetInterfaces() const { if (_hasCachedInterfaces) return _interfaces; @@ -1206,7 +1212,7 @@ MObject* MClass::GetAttribute(const MClass* klass) const return (MObject*)GetCustomAttribute(GetAttributes(), klass); } -const Array& MClass::GetAttributes() const +const Array& MClass::GetAttributes() const { if (_hasCachedAttributes) return _attributes; @@ -1239,6 +1245,7 @@ MEvent::MEvent(MClass* parentClass, void* handle, const char* name) , _hasCachedAttributes(false) , _hasAddMonoMethod(true) , _hasRemoveMonoMethod(true) + , _attributes(&parentClass->GetAssembly()->Memory) { } @@ -1267,7 +1274,7 @@ MObject* MEvent::GetAttribute(const MClass* klass) const return (MObject*)GetCustomAttribute(GetAttributes(), klass); } -const Array& MEvent::GetAttributes() const +const Array& MEvent::GetAttributes() const { if (_hasCachedAttributes) return _attributes; @@ -1313,6 +1320,7 @@ MField::MField(MClass* parentClass, void* handle, const char* name, void* type, , _parentClass(parentClass) , _name(parentClass->GetAssembly()->AllocString(name)) , _hasCachedAttributes(false) + , _attributes(&parentClass->GetAssembly()->Memory) { switch (attributes & MFieldAttributes::FieldAccessMask) { @@ -1389,7 +1397,7 @@ MObject* MField::GetAttribute(const MClass* klass) const return (MObject*)GetCustomAttribute(GetAttributes(), klass); } -const Array& MField::GetAttributes() const +const Array& MField::GetAttributes() const { if (_hasCachedAttributes) return _attributes; @@ -1410,6 +1418,7 @@ MMethod::MMethod(MClass* parentClass, StringAnsiView name, void* handle, int32 p , _name(name) , _hasCachedAttributes(false) , _hasCachedSignature(false) + , _attributes(&parentClass->GetAssembly()->Memory) { switch (attributes & MMethodAttributes::MemberAccessMask) { @@ -1555,7 +1564,7 @@ MObject* MMethod::GetAttribute(const MClass* klass) const return (MObject*)GetCustomAttribute(GetAttributes(), klass); } -const Array& MMethod::GetAttributes() const +const Array& MMethod::GetAttributes() const { if (_hasCachedAttributes) return _attributes; @@ -1584,6 +1593,7 @@ MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void* , _name(parentClass->GetAssembly()->AllocString(name)) , _handle(handle) , _hasCachedAttributes(false) + , _attributes(&parentClass->GetAssembly()->Memory) { _hasGetMethod = getterHandle != nullptr; if (_hasGetMethod) @@ -1644,7 +1654,7 @@ MObject* MProperty::GetAttribute(const MClass* klass) const return (MObject*)GetCustomAttribute(GetAttributes(), klass); } -const Array& MProperty::GetAttributes() const +const Array& MProperty::GetAttributes() const { if (_hasCachedAttributes) return _attributes; diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 317f4e798..86b800e97 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -1539,7 +1539,7 @@ MObject* MClass::GetAttribute(const MClass* klass) const return attrInfo ? mono_custom_attrs_get_attr(attrInfo, klass->GetNative()) : nullptr; } -const Array& MClass::GetAttributes() const +const Array& MClass::GetAttributes() const { if (_hasCachedAttributes) return _attributes; @@ -1662,7 +1662,7 @@ MObject* MEvent::GetAttribute(const MClass* klass) const return foundAttr; } -const Array& MEvent::GetAttributes() const +const Array& MEvent::GetAttributes() const { if (_hasCachedAttributes) return _attributes; @@ -1815,7 +1815,7 @@ MObject* MField::GetAttribute(const MClass* klass) const return foundAttr; } -const Array& MField::GetAttributes() const +const Array& MField::GetAttributes() const { if (_hasCachedAttributes) return _attributes; @@ -1988,7 +1988,7 @@ MObject* MMethod::GetAttribute(const MClass* klass) const return foundAttr; } -const Array& MMethod::GetAttributes() const +const Array& MMethod::GetAttributes() const { if (_hasCachedAttributes) return _attributes; @@ -2118,7 +2118,7 @@ MObject* MProperty::GetAttribute(const MClass* klass) const return foundAttr; } -const Array& MProperty::GetAttributes() const +const Array& MProperty::GetAttributes() const { if (_hasCachedAttributes) return _attributes; diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index a7c921cb0..580029eef 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -358,7 +358,7 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const return nullptr; } -const Array& MClass::GetMethods() const +const Array& MClass::GetMethods() const { _hasCachedMethods = true; return _methods; @@ -369,13 +369,13 @@ MField* MClass::GetField(const char* name) const return nullptr; } -const Array& MClass::GetFields() const +const Array& MClass::GetFields() const { _hasCachedFields = true; return _fields; } -const Array& MClass::GetEvents() const +const Array& MClass::GetEvents() const { _hasCachedEvents = true; return _events; @@ -386,7 +386,7 @@ MProperty* MClass::GetProperty(const char* name) const return nullptr; } -const Array& MClass::GetProperties() const +const Array& MClass::GetProperties() const { _hasCachedProperties = true; return _properties; @@ -407,7 +407,7 @@ MObject* MClass::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MClass::GetAttributes() const +const Array& MClass::GetAttributes() const { _hasCachedAttributes = true; return _attributes; @@ -449,7 +449,7 @@ MObject* MEvent::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MEvent::GetAttributes() const +const Array& MEvent::GetAttributes() const { return _attributes; } @@ -501,7 +501,7 @@ MObject* MField::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MField::GetAttributes() const +const Array& MField::GetAttributes() const { return _attributes; } @@ -556,7 +556,7 @@ MObject* MMethod::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MMethod::GetAttributes() const +const Array& MMethod::GetAttributes() const { return _attributes; } @@ -603,7 +603,7 @@ MObject* MProperty::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MProperty::GetAttributes() const +const Array& MProperty::GetAttributes() const { return _attributes; } From 98e59450f1bd0f825e070a87db8e362cad34a2fa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 25 May 2025 17:39:51 +0200 Subject: [PATCH 13/84] Add freeing managed assembly memory on reload/unload --- Source/Engine/Core/Memory/Allocation.cpp | 7 +++++-- Source/Engine/Scripting/ManagedCLR/MCore.cpp | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Memory/Allocation.cpp b/Source/Engine/Core/Memory/Allocation.cpp index 87c9dbc63..59b3a8a7e 100644 --- a/Source/Engine/Core/Memory/Allocation.cpp +++ b/Source/Engine/Core/Memory/Allocation.cpp @@ -14,6 +14,9 @@ void ArenaAllocator::Free() Allocator::Free(page); page = next; } + + // Unlink + _first = nullptr; } void* ArenaAllocator::Allocate(uint64 size, uint64 alignment) @@ -31,14 +34,14 @@ void* ArenaAllocator::Allocate(uint64 size, uint64 alignment) page->Memory = Allocator::Allocate(pageSize); page->Next = _first; page->Offset = 0; - page->Size = pageSize; + page->Size = (uint32)pageSize; _first = page; } // Allocate within a page page->Offset = Math::AlignUp(page->Offset, (uint32)alignment); void* mem = (byte*)page->Memory + page->Offset; - page->Offset += size; + page->Offset += (uint32)size; return mem; } \ No newline at end of file diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 4ded56b52..675e09ddf 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -143,6 +143,7 @@ void MAssembly::Unload(bool isReloading) #else _classes.ClearDelete(); #endif + Memory.Free(); Unloaded(this); } From 8f9fa6995eafe270a95d6a25da16d5e22d542070 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 25 May 2025 17:40:00 +0200 Subject: [PATCH 14/84] Fix compilation issues --- Source/Engine/Platform/Apple/ApplePlatform.cpp | 13 +++++++++++++ Source/Engine/Platform/Win32/Win32Platform.cpp | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index ad86ed7da..bbb08ddaf 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -2,6 +2,9 @@ #if PLATFORM_MAC || PLATFORM_IOS +#define PLATFORM_MAC_CACHED PLATFORM_MAC +#define PLATFORM_IOS_CACHED PLATFORM_IOS + #include "ApplePlatform.h" #include "AppleUtils.h" #include "Engine/Core/Log.h" @@ -50,6 +53,10 @@ #include #endif +// System includes break those defines +#define PLATFORM_MAC PLATFORM_MAC_CACHED +#define PLATFORM_IOS PLATFORM_IOS_CACHED + CPUInfo Cpu; String UserLocale; double SecondsPerCycle; @@ -224,10 +231,16 @@ MemoryStats ApplePlatform::GetMemoryStats() ProcessMemoryStats ApplePlatform::GetProcessMemoryStats() { ProcessMemoryStats result; +#if PLATFORM_IOS + rusage_info_current rusage_payload; + proc_pid_rusage(getpid(), RUSAGE_INFO_CURRENT, (rusage_info_t*)&rusage_payload); + result.UsedPhysicalMemory = rusage_payload.ri_phys_footprint; +#else mach_task_basic_info_data_t taskInfo; mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&taskInfo, &count); result.UsedPhysicalMemory = taskInfo.resident_size; +#endif result.UsedVirtualMemory = result.UsedPhysicalMemory; return result; } diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index 6b5eaa2ff..c002986c9 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -290,7 +290,7 @@ void* Win32Platform::AllocatePages(uint64 numPages, uint64 pageSize) if (!ptr) OutOfMemory(); #if COMPILE_WITH_PROFILER - OnMemoryAlloc(ptr, size); + OnMemoryAlloc(ptr, numBytes); #endif return ptr; } From a74c5e79432e71f3e2ac0c32e13b68a6a87c7738 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 25 May 2025 18:01:30 +0200 Subject: [PATCH 15/84] Another fix for iOS build --- .../Engine/Platform/Apple/ApplePlatform.cpp | 27 ++++++++++++++++--- Source/Engine/Scripting/Runtime/DotNet.cpp | 2 +- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index bbb08ddaf..c6c689195 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -2,8 +2,12 @@ #if PLATFORM_MAC || PLATFORM_IOS -#define PLATFORM_MAC_CACHED PLATFORM_MAC -#define PLATFORM_IOS_CACHED PLATFORM_IOS +#if PLATFORM_MAC +#define PLATFORM_MAC_CACHED 1 +#endif +#if PLATFORM_IOS +#define PLATFORM_IOS_CACHED 1 +#endif #include "ApplePlatform.h" #include "AppleUtils.h" @@ -54,8 +58,23 @@ #endif // System includes break those defines -#define PLATFORM_MAC PLATFORM_MAC_CACHED -#define PLATFORM_IOS PLATFORM_IOS_CACHED +#undef PLATFORM_MAC +#if PLATFORM_MAC_CACHED +#define PLATFORM_MAC 1 +#else +#define PLATFORM_MAC 0 +#endif +#undef PLATFORM_IOS +#if PLATFORM_IOS_CACHED +#define PLATFORM_IOS 1 +#else +#define PLATFORM_IOS 0 +#endif + +#if PLATFORM_IOS +#include +extern "C" int proc_pid_rusage(int pid, int flavor, rusage_info_t *buffer) __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); +#endif CPUInfo Cpu; String UserLocale; diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index ce5f315f1..24e07859d 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -2272,7 +2272,7 @@ void ShutdownHostfxr() #endif } -void* GetStaticMethodPointer(const String& methodName) +void* GetStaticMethodPointer(StringView methodName) { void* fun; if (CachedFunctions.TryGet(methodName, fun)) From 9dc4dbc6d775aceb8e1971e4f6e03c9aed26bbba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 25 May 2025 18:38:07 +0200 Subject: [PATCH 16/84] Add more memory profiler categories --- Source/Engine/Content/Assets/VisualScript.cpp | 15 +++++ Source/Engine/Core/Memory/Allocation.cpp | 7 +++ Source/Engine/Graphics/GPUBuffer.cpp | 20 ++++++- .../Engine/Graphics/Textures/GPUTexture.cpp | 24 +++++++- Source/Engine/Profiler/ProfilerMemory.cpp | 55 ++++++++----------- Source/Engine/Profiler/ProfilerMemory.h | 29 +++++++++- 6 files changed, 112 insertions(+), 38 deletions(-) diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index a6d83919c..2e6ffb735 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -18,6 +18,7 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/JsonWriter.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Threading/MainThreadTask.h" #include "Engine/Level/SceneObject.h" @@ -1340,6 +1341,8 @@ bool VisualScript::Save(const StringView& path) Asset::LoadResult VisualScript::load() { + PROFILE_MEM(ScriptingVisual); + // Build Visual Script typename that is based on asset id String typeName = _id.ToString(); StringUtils::ConvertUTF162ANSI(typeName.Get(), _typenameChars, 32); @@ -1532,6 +1535,7 @@ Asset::LoadResult VisualScript::load() void VisualScript::unload(bool isReloading) { + PROFILE_MEM(ScriptingVisual); #if USE_EDITOR if (isReloading) { @@ -1588,6 +1592,7 @@ AssetChunksFlag VisualScript::getChunksToPreload() const void VisualScript::CacheScriptingType() { + PROFILE_MEM(ScriptingVisual); ScopeLock lock(VisualScriptingBinaryModule::Locker); auto& binaryModule = VisualScriptingModule; @@ -1723,6 +1728,7 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri VisualScript* visualScript = VisualScriptingModule.Scripts[params.Type.TypeIndex]; // Initialize instance data + PROFILE_MEM(ScriptingVisual); ScopeLock lock(visualScript->Locker); auto& instanceParams = visualScript->_instances[object->GetID()].Params; instanceParams.Resize(visualScript->Graph.Parameters.Count()); @@ -1747,6 +1753,8 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri void VisualScriptingBinaryModule::OnScriptsReloading() { + PROFILE_MEM(ScriptingVisual); + // Clear any cached types from that module across all loaded Visual Scripts for (auto& script : Scripts) { @@ -1795,6 +1803,7 @@ void VisualScriptingBinaryModule::OnScriptsReloading() void VisualScriptingBinaryModule::OnEvent(ScriptingObject* object, Span parameters, ScriptingTypeHandle eventType, StringView eventName) { + PROFILE_MEM(ScriptingVisual); if (object) { // Object event @@ -1956,6 +1965,7 @@ bool VisualScriptingBinaryModule::GetFieldValue(void* field, const Variant& inst bool VisualScriptingBinaryModule::SetFieldValue(void* field, const Variant& instance, Variant& value) { + PROFILE_MEM(ScriptingVisual); const auto vsFiled = (VisualScript::Field*)field; const auto instanceObject = (ScriptingObject*)instance; if (!instanceObject) @@ -2042,6 +2052,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO void VisualScriptingBinaryModule::DeserializeObject(ISerializable::DeserializeStream& stream, ScriptingObject* object, ISerializeModifier* modifier) { + PROFILE_MEM(ScriptingVisual); ASSERT(stream.IsObject()); Locker.Lock(); const auto asset = Scripts[object->GetTypeHandle().TypeIndex].Get(); @@ -2165,6 +2176,7 @@ const Variant& VisualScript::GetScriptInstanceParameterValue(const StringView& n void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, const Variant& value) { + PROFILE_MEM(ScriptingVisual); CHECK(instance); for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++) { @@ -2186,6 +2198,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, Variant&& value) { + PROFILE_MEM(ScriptingVisual); CHECK(instance); for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++) { @@ -2383,6 +2396,7 @@ VisualScriptingBinaryModule* VisualScripting::GetBinaryModule() Variant VisualScripting::Invoke(VisualScript::Method* method, ScriptingObject* instance, Span parameters) { + PROFILE_MEM(ScriptingVisual); CHECK_RETURN(method && method->Script->IsLoaded(), Variant::Zero); PROFILE_CPU_SRC_LOC(method->ProfilerData); @@ -2423,6 +2437,7 @@ bool VisualScripting::Evaluate(VisualScript* script, ScriptingObject* instance, const auto box = node->GetBox(boxId); if (!box) return false; + PROFILE_MEM(ScriptingVisual); // Add to the calling stack ScopeContext scope; diff --git a/Source/Engine/Core/Memory/Allocation.cpp b/Source/Engine/Core/Memory/Allocation.cpp index 59b3a8a7e..b2e74b8b6 100644 --- a/Source/Engine/Core/Memory/Allocation.cpp +++ b/Source/Engine/Core/Memory/Allocation.cpp @@ -2,6 +2,7 @@ #include "ArenaAllocation.h" #include "../Math/Math.h" +#include "Engine/Profiler/ProfilerMemory.h" void ArenaAllocator::Free() { @@ -9,6 +10,9 @@ void ArenaAllocator::Free() Page* page = _first; while (page) { +#if COMPILE_WITH_PROFILER + ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -page->Size, -1); +#endif Allocator::Free(page->Memory); Page* next = page->Next; Allocator::Free(page); @@ -30,6 +34,9 @@ void* ArenaAllocator::Allocate(uint64 size, uint64 alignment) if (!page) { uint64 pageSize = Math::Max(_pageSize, size); +#if COMPILE_WITH_PROFILER + ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, pageSize, 1); +#endif page = (Page*)Allocator::Allocate(sizeof(Page)); page->Memory = Allocator::Allocate(pageSize); page->Next = _first; diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index db7845227..f8372572e 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -244,7 +244,15 @@ bool GPUBuffer::Init(const GPUBufferDescription& desc) LOG(Warning, "Cannot initialize buffer. Description: {0}", desc.ToString()); return true; } - PROFILE_MEM_INC(GraphicsBuffers, GetMemoryUsage()); + +#if COMPILE_WITH_PROFILER + auto group = ProfilerMemory::Groups::GraphicsBuffers; + if (EnumHasAnyFlags(_desc.Flags, GPUBufferFlags::VertexBuffer)) + group = ProfilerMemory::Groups::GraphicsVertexBuffers; + else if (EnumHasAnyFlags(_desc.Flags, GPUBufferFlags::IndexBuffer)) + group = ProfilerMemory::Groups::GraphicsIndexBuffers; + ProfilerMemory::IncrementGroup(group, _memoryUsage); +#endif return false; } @@ -480,7 +488,15 @@ GPUResourceType GPUBuffer::GetResourceType() const void GPUBuffer::OnReleaseGPU() { - PROFILE_MEM_DEC(GraphicsBuffers, GetMemoryUsage()); +#if COMPILE_WITH_PROFILER + auto group = ProfilerMemory::Groups::GraphicsBuffers; + if (EnumHasAnyFlags(_desc.Flags, GPUBufferFlags::VertexBuffer)) + group = ProfilerMemory::Groups::GraphicsVertexBuffers; + else if (EnumHasAnyFlags(_desc.Flags, GPUBufferFlags::IndexBuffer)) + group = ProfilerMemory::Groups::GraphicsIndexBuffers; + ProfilerMemory::IncrementGroup(group, _memoryUsage); +#endif + _desc.Clear(); _isLocked = false; } diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 6d138a40c..245fb1e01 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -503,7 +503,17 @@ bool GPUTexture::Init(const GPUTextureDescription& desc) LOG(Warning, "Cannot initialize texture. Description: {0}", desc.ToString()); return true; } - PROFILE_MEM_INC(GraphicsTextures, GetMemoryUsage()); + +#if COMPILE_WITH_PROFILER + auto group = ProfilerMemory::Groups::GraphicsTextures; + if (_desc.IsRenderTarget()) + group = ProfilerMemory::Groups::GraphicsRenderTargets; + else if (_desc.IsCubeMap()) + group = ProfilerMemory::Groups::GraphicsCubeMaps; + else if (_desc.IsVolume()) + group = ProfilerMemory::Groups::GraphicsVolumeTextures; + ProfilerMemory::IncrementGroup(group, _memoryUsage); +#endif // Render targets and depth buffers doesn't support normal textures streaming and are considered to be always resident if (IsRegularTexture() == false) @@ -593,7 +603,17 @@ GPUResourceType GPUTexture::GetResourceType() const void GPUTexture::OnReleaseGPU() { - PROFILE_MEM_DEC(GraphicsTextures, GetMemoryUsage()); +#if COMPILE_WITH_PROFILER + auto group = ProfilerMemory::Groups::GraphicsTextures; + if (_desc.IsRenderTarget()) + group = ProfilerMemory::Groups::GraphicsRenderTargets; + else if (_desc.IsCubeMap()) + group = ProfilerMemory::Groups::GraphicsCubeMaps; + else if (_desc.IsVolume()) + group = ProfilerMemory::Groups::GraphicsVolumeTextures; + ProfilerMemory::DecrementGroup(group, _memoryUsage); +#endif + _desc.Clear(); _residentMipLevels = 0; } diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index adb244f26..f05ac5f64 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -25,14 +25,14 @@ struct GroupNameBuffer Char Buffer[30]; template - void Set(const T* str) + void Set(const T* str, bool autoFormat = false) { 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)) + if (autoFormat && StringUtils::IsUpper(cur) && StringUtils::IsLower(prev)) Buffer[dst++] = '/'; Buffer[dst++] = cur; prev = cur; @@ -52,10 +52,6 @@ struct GroupStackData { if (Count < ARRAY_COUNT(Stack)) Count++; - else - { - int a= 10; - } Stack[Count - 1] = (uint8)group; } @@ -112,8 +108,15 @@ namespace for (int32 i = 0; i < GROUPS_COUNT; i++) { const char* name = ScriptingEnum::GetName((ProfilerMemory::Groups)i); - GroupNames[i].Set(name); + GroupNames[i].Set(name, true); } +#define RENAME_GROUP(group, name) GroupNames[(int32)ProfilerMemory::Groups::group].Set(name) + RENAME_GROUP(GraphicsRenderTargets, "Graphics/RenderTargets"); + RENAME_GROUP(GraphicsCubeMaps, "Graphics/CubeMaps"); + RENAME_GROUP(GraphicsVolumeTextures, "Graphics/VolumeTextures"); + RENAME_GROUP(GraphicsVertexBuffers, "Graphics/VertexBuffers"); + RENAME_GROUP(GraphicsIndexBuffers, "Graphics/IndexBuffers"); +#undef RENAME_GROUP // Init constant memory PROFILE_MEM_INC(ProgramSize, Platform::GetMemoryStats().ProgramSizeMemory); @@ -162,31 +165,6 @@ namespace 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 - // Warn that data might be missing due to inactive profiler if (!ProfilerMemory::Enabled) output.AppendLine(TEXT("Detailed memory profiling is disabled. Run with command line: -mem")); @@ -243,8 +221,14 @@ void InitProfilerMemory(const Char* cmdLine) // Init hierarchy #define INIT_PARENT(parent, child) GroupParents[(int32)ProfilerMemory::Groups::child] = (uint8)ProfilerMemory::Groups::parent + INIT_PARENT(Malloc, MallocArena); INIT_PARENT(Graphics, GraphicsTextures); + INIT_PARENT(Graphics, GraphicsRenderTargets); + INIT_PARENT(Graphics, GraphicsCubeMaps); + INIT_PARENT(Graphics, GraphicsVolumeTextures); INIT_PARENT(Graphics, GraphicsBuffers); + INIT_PARENT(Graphics, GraphicsVertexBuffers); + INIT_PARENT(Graphics, GraphicsIndexBuffers); INIT_PARENT(Graphics, GraphicsMeshes); INIT_PARENT(Graphics, GraphicsShaders); INIT_PARENT(Graphics, GraphicsMaterials); @@ -414,4 +398,11 @@ void ProfilerMemory::OnMemoryFree(void* ptr) stack.SkipRecursion = false; } +void ProfilerMemory::OnGroupUpdate(Groups group, int64 sizeDelta, int64 countDetla) +{ + Platform::InterlockedAdd(&GroupMemory[(int32)group], sizeDelta); + Platform::InterlockedAdd(&GroupMemoryCount[(int32)group], countDetla); + UPDATE_PEEK(group); +} + #endif diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h index 206814560..a9dedadad 100644 --- a/Source/Engine/Profiler/ProfilerMemory.h +++ b/Source/Engine/Profiler/ProfilerMemory.h @@ -30,19 +30,32 @@ public: 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 memory allocated via dynamic memory allocations. + Malloc, + // Total memory allocated via arena allocators (all pages). + MallocArena, + // Total graphics memory usage. Graphics, // Total textures memory usage. GraphicsTextures, + // Total render targets memory usage (textures used as target image for rendering). + GraphicsRenderTargets, + // Total cubemap textures memory usage (each cubemap is 6 textures). + GraphicsCubeMaps, + // Total volume textures memory usage (3D textures). + GraphicsVolumeTextures, // Total buffers memory usage. GraphicsBuffers, + // Total vertex buffers memory usage. + GraphicsVertexBuffers, + // Total index buffers memory usage. + GraphicsIndexBuffers, // Total meshes memory usage (vertex and idnex buffers allocated by models). GraphicsMeshes, // Totoal shaders memory usage (shaders bytecode, PSOs data). @@ -95,6 +108,8 @@ public: // Total scripting memory allocated by game. Scripting, + // Total Visual scripting memory allocated by game (visual script graphs, data and runtime allocations). + ScriptingVisual, // Total User Interface components memory. UI, @@ -144,6 +159,15 @@ public: // Custom plugin-specific memory tracking. CustomPlugin9, + // Custom platform-specific memory tracking. + CustomPlatform0, + // Custom platform-specific memory tracking. + CustomPlatform1, + // Custom platform-specific memory tracking. + CustomPlatform2, + // Custom platform-specific memory tracking. + CustomPlatform3, + // Total editor-specific memory. Editor, @@ -219,6 +243,7 @@ public: static void OnMemoryAlloc(void* ptr, uint64 size); static void OnMemoryFree(void* ptr); + static void OnGroupUpdate(Groups group, int64 sizeDelta, int64 countDetla); public: /// From 72ee80242d812b9074f0e6b0102b5ba42a55e99c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 26 May 2025 05:37:53 +0200 Subject: [PATCH 17/84] Add integration with Tracy profiler to plot main memory categories --- Source/Engine/Engine/Engine.cpp | 7 +- Source/Engine/Profiler/ProfilerMemory.cpp | 112 +++++++++++++++------- 2 files changed, 85 insertions(+), 34 deletions(-) diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 288e0da11..17a2f377b 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -80,8 +80,8 @@ Window* Engine::MainWindow = nullptr; int32 Engine::Main(const Char* cmdLine) { #if COMPILE_WITH_PROFILER - extern void InitProfilerMemory(const Char*); - InitProfilerMemory(cmdLine); + extern void InitProfilerMemory(const Char* cmdLine, int32 stage); + InitProfilerMemory(cmdLine, 0); #endif PROFILE_MEM_BEGIN(Engine); EngineImpl::CommandLine = cmdLine; @@ -109,6 +109,9 @@ int32 Engine::Main(const Char* cmdLine) Platform::Fatal(TEXT("Cannot init platform.")); return -1; } +#if COMPILE_WITH_PROFILER + InitProfilerMemory(cmdLine, 1); +#endif Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue()); Time::StartupTime = DateTime::Now(); diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index f05ac5f64..a18004fc1 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -14,8 +14,10 @@ #include "Engine/Scripting/Enums.h" #include "Engine/Threading/ThreadLocal.h" #include "Engine/Utilities/StringConverter.h" +#include #define GROUPS_COUNT (int32)ProfilerMemory::Groups::MAX +#define USE_TRACY_MEMORY_PLOTS (defined(TRACY_ENABLE)) static_assert(GROUPS_COUNT <= MAX_uint8, "Fix memory profiler groups to fit a single byte."); @@ -23,6 +25,7 @@ static_assert(GROUPS_COUNT <= MAX_uint8, "Fix memory profiler groups to fit a si struct GroupNameBuffer { Char Buffer[30]; + char Ansi[30]; template void Set(const T* str, bool autoFormat = false) @@ -33,11 +36,16 @@ struct GroupNameBuffer { char cur = str[i]; if (autoFormat && StringUtils::IsUpper(cur) && StringUtils::IsLower(prev)) + { + Ansi[dst] = '/'; Buffer[dst++] = '/'; + } + Ansi[dst] = cur; Buffer[dst++] = cur; prev = cur; } Buffer[dst] = 0; + Ansi[dst] = 0; } }; @@ -93,40 +101,17 @@ namespace alignas(16) volatile int64 GroupMemory[GROUPS_COUNT] = {}; alignas(16) volatile int64 GroupMemoryPeek[GROUPS_COUNT] = {}; alignas(16) volatile int64 GroupMemoryCount[GROUPS_COUNT] = {}; +#ifdef USE_TRACY_MEMORY_PLOTS + alignas(16) volatile uint32 GroupTracyPlotEnable[(GROUPS_COUNT + 31) / 32] = {}; +#endif uint8 GroupParents[GROUPS_COUNT] = {}; ThreadLocal GroupStack; GroupNameBuffer GroupNames[GROUPS_COUNT]; - bool InitedNames = false; CriticalSection PointersLocker; Dictionary 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, true); - } -#define RENAME_GROUP(group, name) GroupNames[(int32)ProfilerMemory::Groups::group].Set(name) - RENAME_GROUP(GraphicsRenderTargets, "Graphics/RenderTargets"); - RENAME_GROUP(GraphicsCubeMaps, "Graphics/CubeMaps"); - RENAME_GROUP(GraphicsVolumeTextures, "Graphics/VolumeTextures"); - RENAME_GROUP(GraphicsVertexBuffers, "Graphics/VertexBuffers"); - RENAME_GROUP(GraphicsIndexBuffers, "Graphics/IndexBuffers"); -#undef RENAME_GROUP - - // 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 { @@ -170,11 +155,26 @@ namespace output.AppendLine(TEXT("Detailed memory profiling is disabled. Run with command line: -mem")); } +#ifdef USE_TRACY_MEMORY_PLOTS + FORCE_INLINE void UpdateGroupTracyPlot(ProfilerMemory::Groups group) + { + // Track only selected groups in Tracy + uint32 bit = (uint32)(1 << ((int32)group & 31)); + if ((GroupTracyPlotEnable[(int32)group / 32] & bit) == bit) + { + TracyPlot(GroupNames[(int32)group].Ansi, GroupMemory[(int32)group]); + } + } +#else +#define UpdateGroupTracyPlot(group) +#endif + FORCE_INLINE void AddGroupMemory(ProfilerMemory::Groups group, int64 add) { // Group itself Platform::InterlockedAdd(&GroupMemory[(int32)group], add); Platform::InterlockedIncrement(&GroupMemoryCount[(int32)group]); + UpdateGroupTracyPlot(group); UPDATE_PEEK(group); // Total memory @@ -188,6 +188,7 @@ namespace { Platform::InterlockedAdd(&GroupMemory[parent], add); Platform::InterlockedIncrement(&GroupMemoryCount[parent]); + UpdateGroupTracyPlot((ProfilerMemory::Groups)parent); UPDATE_PEEK(parent); parent = GroupParents[parent]; } @@ -196,26 +197,37 @@ namespace FORCE_INLINE void SubGroupMemory(ProfilerMemory::Groups group, int64 add) { // Group itself - int64 value = Platform::InterlockedAdd(&GroupMemory[(int32)group], add); + Platform::InterlockedAdd(&GroupMemory[(int32)group], add); Platform::InterlockedDecrement(&GroupMemoryCount[(int32)group]); + UpdateGroupTracyPlot(group); // Total memory - value = Platform::InterlockedAdd(&GroupMemory[(int32)ProfilerMemory::Groups::TotalTracked], add); + 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::InterlockedAdd(&GroupMemory[parent], add); Platform::InterlockedDecrement(&GroupMemoryCount[parent]); + UpdateGroupTracyPlot((ProfilerMemory::Groups)parent); parent = GroupParents[parent]; } } } -void InitProfilerMemory(const Char* cmdLine) +void InitProfilerMemory(const Char* cmdLine, int32 stage) { + if (stage == 1) // Post-platform init + { + // Init constant memory + PROFILE_MEM_INC(ProgramSize, Platform::GetMemoryStats().ProgramSizeMemory); + UPDATE_PEEK(ProfilerMemory::Groups::ProgramSize); + + return; + } + // Check for command line option (memory profiling affects performance thus not active by default) ProfilerMemory::Enabled = StringUtils::FindIgnoreCase(cmdLine, TEXT("-mem")); @@ -237,6 +249,44 @@ void InitProfilerMemory(const Char* cmdLine) INIT_PARENT(Content, ContentAssets); INIT_PARENT(Content, ContentFiles); #undef INIT_PARENT + + // Init group names + for (int32 i = 0; i < GROUPS_COUNT; i++) + { + const char* name = ScriptingEnum::GetName((ProfilerMemory::Groups)i); + GroupNames[i].Set(name, true); + } +#define RENAME_GROUP(group, name) GroupNames[(int32)ProfilerMemory::Groups::group].Set(name) + RENAME_GROUP(GraphicsRenderTargets, "Graphics/RenderTargets"); + RENAME_GROUP(GraphicsCubeMaps, "Graphics/CubeMaps"); + RENAME_GROUP(GraphicsVolumeTextures, "Graphics/VolumeTextures"); + RENAME_GROUP(GraphicsVertexBuffers, "Graphics/VertexBuffers"); + RENAME_GROUP(GraphicsIndexBuffers, "Graphics/IndexBuffers"); +#undef RENAME_GROUP + + // Init Tracy +#ifdef USE_TRACY_MEMORY_PLOTS + // Toggle on specific groups only for high-level overview only +#define ENABLE_GROUP(group) GroupTracyPlotEnable[(uint32)ProfilerMemory::Groups::group / 32] |= (uint32)(1 << ((int32)ProfilerMemory::Groups::group & 31)) + ENABLE_GROUP(Graphics); + ENABLE_GROUP(Audio); + ENABLE_GROUP(Content); + ENABLE_GROUP(Level); + ENABLE_GROUP(Physics); + ENABLE_GROUP(Scripting); + ENABLE_GROUP(UI); +#undef ENABLE_GROUP + + // Setup plots + for (int32 i = 0; i < GROUPS_COUNT; i++) + { + uint32 bit = (uint32)(1 << ((int32)i & 31)); + if ((GroupTracyPlotEnable[i / 32] & bit) == bit) + { + TracyPlotConfig(GroupNames[i].Ansi, tracy::PlotFormatType::Memory, false, true, 0); + } + } +#endif } void TickProfilerMemory() @@ -294,7 +344,6 @@ Array ProfilerMemory::GetGroupNames() { Array result; result.Resize((int32)Groups::MAX); - InitNames(); for (int32 i = 0; i < (int32)Groups::MAX; i++) result[i] = GroupNames[i].Buffer; return result; @@ -305,7 +354,6 @@ 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++) From ab61ed5a375cc83da470dda8e2ac6f2db60cb820 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 May 2025 04:03:44 +0200 Subject: [PATCH 18/84] Add more memory profiling insights and groups --- Source/Editor/Scripting/ScriptsBuilder.cpp | 3 ++ Source/Engine/Audio/AudioClip.cpp | 1 + Source/Engine/Content/JsonAsset.cpp | 1 + .../Engine/Engine/NativeInterop.Unmanaged.cs | 8 ++++ Source/Engine/Level/Level.cpp | 1 + Source/Engine/Navigation/NavCrowd.cpp | 4 ++ Source/Engine/Profiler/ProfilerMemory.cpp | 24 +++++++++++- Source/Engine/Profiler/ProfilerMemory.h | 6 +++ .../Engine/Renderer/AtmospherePreCompute.cpp | 3 ++ Source/Engine/Scripting/BinaryModule.cpp | 8 ++-- Source/Engine/Scripting/ManagedCLR/MCore.cpp | 2 +- Source/Engine/Scripting/ManagedCLR/MCore.h | 1 + Source/Engine/Scripting/Runtime/DotNet.cpp | 38 +++++++++++-------- Source/Engine/Scripting/Runtime/Mono.cpp | 5 +++ Source/Engine/Scripting/Runtime/None.cpp | 5 +++ Source/Engine/Scripting/Scripting.cpp | 1 + 16 files changed, 89 insertions(+), 22 deletions(-) diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index ea58f3240..8f33d04d7 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -664,6 +664,9 @@ bool ScriptsBuilderService::Init() void ScriptsBuilderService::Update() { + PROFILE_CPU(); + PROFILE_MEM(Editor); + // Send compilation events { ScopeLock scopeLock(_compileEventsLocker); diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 2ac9d218c..2ca7f3512 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -19,6 +19,7 @@ REGISTER_BINARY_ASSET_WITH_UPGRADER(AudioClip, "FlaxEngine.AudioClip", AudioClip bool AudioClip::StreamingTask::Run() { + PROFILE_MEM(Audio); AssetReference ref = _asset.Get(); if (ref == nullptr || AudioBackend::Instance == nullptr) return true; diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 04487eb65..1aa434c41 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -310,6 +310,7 @@ Asset::LoadResult JsonAssetBase::loadAsset() void JsonAssetBase::unload(bool isReloading) { + PROFILE_MEM(ContentAssets); ISerializable::SerializeDocument tmp; Document.Swap(tmp); Data = nullptr; diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 7692f375e..7c1e2e64a 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -1277,6 +1277,14 @@ namespace FlaxEngine.Interop return GC.MaxGeneration; } + [UnmanagedCallersOnly] + internal static void GCMemoryInfo(long* totalCommitted, long* heapSize) + { + GCMemoryInfo gcMemoryInfo = GC.GetGCMemoryInfo(); + *totalCommitted = gcMemoryInfo.TotalCommittedBytes; + *heapSize = gcMemoryInfo.HeapSizeBytes; + } + [UnmanagedCallersOnly] internal static void GCWaitForPendingFinalizers() { diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 5bcd98994..1233282be 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -813,6 +813,7 @@ bool LevelImpl::unloadScene(Scene* scene) bool LevelImpl::unloadScenes() { + PROFILE_MEM(Level); auto scenes = Level::Scenes; for (int32 i = scenes.Count() - 1; i >= 0; i--) { diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp index cb2a4ebee..ed7f3ba7f 100644 --- a/Source/Engine/Navigation/NavCrowd.cpp +++ b/Source/Engine/Navigation/NavCrowd.cpp @@ -7,12 +7,14 @@ #include "Engine/Level/Level.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Threading/Threading.h" #include NavCrowd::NavCrowd(const SpawnParams& params) : ScriptingObject(params) { + PROFILE_MEM(Navigation); _crowd = dtAllocCrowd(); } @@ -51,6 +53,7 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe if (!_crowd || !navMesh) return true; PROFILE_CPU(); + PROFILE_MEM(Navigation); // This can happen on game start when no navmesh is loaded yet (eg. navmesh tiles data is during streaming) so wait for navmesh if (navMesh->GetNavMesh() == nullptr) @@ -175,6 +178,7 @@ void NavCrowd::RemoveAgent(int32 id) void NavCrowd::Update(float dt) { PROFILE_CPU(); + PROFILE_MEM(Navigation); _crowd->update(Math::Max(dt, ZeroTolerance), nullptr); } diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index a18004fc1..61433c0e7 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -12,6 +12,7 @@ #include "Engine/Platform/MemoryStats.h" #include "Engine/Platform/File.h" #include "Engine/Scripting/Enums.h" +#include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Threading/ThreadLocal.h" #include "Engine/Utilities/StringConverter.h" #include @@ -24,8 +25,8 @@ static_assert(GROUPS_COUNT <= MAX_uint8, "Fix memory profiler groups to fit a si // Compact name storage. struct GroupNameBuffer { - Char Buffer[30]; - char Ansi[30]; + Char Buffer[40]; + char Ansi[40]; template void Set(const T* str, bool autoFormat = false) @@ -248,6 +249,10 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage) INIT_PARENT(Animations, AnimationsData); INIT_PARENT(Content, ContentAssets); INIT_PARENT(Content, ContentFiles); + INIT_PARENT(Scripting, ScriptingVisual); + INIT_PARENT(Scripting, ScriptingCSharp); + INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCCommitted); + INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCHeap); #undef INIT_PARENT // Init group names @@ -262,6 +267,8 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage) RENAME_GROUP(GraphicsVolumeTextures, "Graphics/VolumeTextures"); RENAME_GROUP(GraphicsVertexBuffers, "Graphics/VertexBuffers"); RENAME_GROUP(GraphicsIndexBuffers, "Graphics/IndexBuffers"); + RENAME_GROUP(ScriptingCSharpGCCommitted, "Scripting/CSharp/GC/Committed"); + RENAME_GROUP(ScriptingCSharpGCHeap, "Scripting/CSharp/GC/Heap"); #undef RENAME_GROUP // Init Tracy @@ -291,10 +298,23 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage) void TickProfilerMemory() { + // Update .NET GC memory stats + int64 totalCommitted, heapSize; + MCore::GC::MemoryInfo(totalCommitted, heapSize); + int64 gcComittedDelta = totalCommitted - GroupMemory[(int32)ProfilerMemory::Groups::ScriptingCSharpGCCommitted]; + GroupMemory[(int32)ProfilerMemory::Groups::ScriptingCSharpGCCommitted] = totalCommitted; + GroupMemory[(int32)ProfilerMemory::Groups::ScriptingCSharpGCHeap] = heapSize; + UPDATE_PEEK(ProfilerMemory::Groups::ScriptingCSharpGCCommitted); + UPDATE_PEEK(ProfilerMemory::Groups::ScriptingCSharpGCHeap); + Platform::InterlockedAdd(&GroupMemory[(int32)ProfilerMemory::Groups::TotalTracked], gcComittedDelta); + // Update profiler memory PointersLocker.Lock(); GroupMemory[(int32)ProfilerMemory::Groups::Profiler] = sizeof(GroupMemory) + sizeof(GroupNames) + sizeof(GroupStack) + +#ifdef USE_TRACY_MEMORY_PLOTS + sizeof(GroupTracyPlotEnable) + +#endif Pointers.Capacity() * sizeof(Dictionary::Bucket); PointersLocker.Unlock(); diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h index a9dedadad..1b1ad5ac3 100644 --- a/Source/Engine/Profiler/ProfilerMemory.h +++ b/Source/Engine/Profiler/ProfilerMemory.h @@ -110,6 +110,12 @@ public: Scripting, // Total Visual scripting memory allocated by game (visual script graphs, data and runtime allocations). ScriptingVisual, + // Total C# scripting memory allocated by game (runtime assemblies, managed interop and runtime allocations). + ScriptingCSharp, + // Total amount of committed virtual memory in use by the .NET GC, as observed during the latest garbage collection. + ScriptingCSharpGCCommitted, + // Total managed GC heap size (including fragmentation), as observed during the latest garbage collection. + ScriptingCSharpGCHeap, // Total User Interface components memory. UI, diff --git a/Source/Engine/Renderer/AtmospherePreCompute.cpp b/Source/Engine/Renderer/AtmospherePreCompute.cpp index 81cee0cb6..595ebcca5 100644 --- a/Source/Engine/Renderer/AtmospherePreCompute.cpp +++ b/Source/Engine/Renderer/AtmospherePreCompute.cpp @@ -342,6 +342,9 @@ void AtmospherePreComputeService::Update() } else if (_isUpdatePending && (_task == nullptr || !_task->Enabled)) { + PROFILE_CPU(); + PROFILE_MEM(Graphics); + // TODO: init but without a stalls, just wait for resources loaded and then start rendering // Init service diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index da01a9f07..ef76de61c 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -763,7 +763,7 @@ ManagedBinaryModule* ManagedBinaryModule::GetModule(const MAssembly* assembly) ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSpawnParams& params) { - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); // Create native object ScriptingTypeHandle managedTypeHandle = params.Type; @@ -935,7 +935,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) { #if !COMPILE_WITHOUT_CSHARP PROFILE_CPU(); - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ASSERT(ClassToTypeIndex.IsEmpty()); ScopeLock lock(Locker); @@ -1032,7 +1032,7 @@ void ManagedBinaryModule::InitType(MClass* mclass) const StringAnsiView typeName = mclass->GetFullName(); if (TypeNameToTypeIndex.ContainsKey(typeName)) return; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); // Find first native base C++ class of this C# class MClass* baseClass = mclass->GetBaseClass(); @@ -1192,7 +1192,7 @@ void ManagedBinaryModule::OnUnloading(MAssembly* assembly) void ManagedBinaryModule::OnUnloaded(MAssembly* assembly) { PROFILE_CPU(); - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); // Clear managed-only types Types.Resize(_firstManagedTypeIndex); diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 675e09ddf..0b38730e8 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -92,7 +92,7 @@ bool MAssembly::Load(const String& assemblyPath, const StringView& nativePath) if (IsLoaded()) return false; PROFILE_CPU(); - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ZoneText(*assemblyPath, assemblyPath.Length()); Stopwatch stopwatch; diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index 549dfadf6..cedebe7b8 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -122,6 +122,7 @@ public: static void Collect(int32 generation); static void Collect(int32 generation, MGCCollectionMode collectionMode, bool blocking, bool compacting); static int32 MaxGeneration(); + static void MemoryInfo(int64& totalCommitted, int64& heapSize); static void WaitForPendingFinalizers(); static void WriteRef(void* ptr, MObject* ref); static void WriteValue(void* dst, void* src, int32 count, const MClass* klass); diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 24e07859d..5b67670e5 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -282,7 +282,7 @@ void MCore::UnloadDomain(const StringAnsi& domainName) bool MCore::LoadEngine() { PROFILE_CPU(); - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); // Initialize hostfxr if (InitHostfxr()) @@ -550,6 +550,12 @@ int32 MCore::GC::MaxGeneration() return maxGeneration; } +void MCore::GC::MemoryInfo(int64& totalCommitted, int64& heapSize) +{ + static void* GCMemoryInfoPtr = GetStaticMethodPointer(TEXT("GCMemoryInfo")); + CallStaticMethod(GCMemoryInfoPtr, &totalCommitted, &heapSize); +} + void MCore::GC::WaitForPendingFinalizers() { PROFILE_CPU(); @@ -737,7 +743,7 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const if (_hasCachedClasses || !IsLoaded()) return _classes; PROFILE_CPU(); - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); Stopwatch stopwatch; #if TRACY_ENABLE @@ -800,7 +806,7 @@ void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullnam DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle) { - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); MAssembly* assembly = GetAssembly(assemblyHandle); if (assembly == nullptr) @@ -836,7 +842,7 @@ bool MAssembly::LoadCorlib() if (IsLoaded()) return false; PROFILE_CPU(); - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); #if TRACY_ENABLE const StringAnsiView name("Corlib"); ZoneText(*name, name.Length()); @@ -1060,7 +1066,7 @@ const Array& MClass::GetMethods() const { if (_hasCachedMethods) return _methods; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedMethods) return _methods; @@ -1099,7 +1105,7 @@ const Array& MClass::GetFields() const { if (_hasCachedFields) return _fields; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedFields) return _fields; @@ -1126,7 +1132,7 @@ const Array& MClass::GetEvents() const { if (_hasCachedEvents) return _events; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); // TODO: implement MEvent in .NET @@ -1149,7 +1155,7 @@ const Array& MClass::GetProperties() const { if (_hasCachedProperties) return _properties; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedProperties) return _properties; @@ -1176,7 +1182,7 @@ const Array& MClass::GetInterfaces() const { if (_hasCachedInterfaces) return _interfaces; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedInterfaces) return _interfaces; @@ -1216,7 +1222,7 @@ const Array& MClass::GetAttributes() const { if (_hasCachedAttributes) return _attributes; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1401,7 +1407,7 @@ const Array& MField::GetAttributes() const { if (_hasCachedAttributes) return _attributes; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1467,7 +1473,7 @@ void MMethod::CacheSignature() const ScopeLock lock(BinaryModule::Locker); if (_hasCachedSignature) return; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType")); static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes")); @@ -1568,7 +1574,7 @@ const Array& MMethod::GetAttributes() const { if (_hasCachedAttributes) return _attributes; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1658,7 +1664,7 @@ const Array& MProperty::GetAttributes() const { if (_hasCachedAttributes) return _attributes; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1689,7 +1695,7 @@ MClass* GetOrCreateClass(MType* typeHandle) { if (!typeHandle) return nullptr; - PROFILE_MEM(Scripting); + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); MClass* klass; if (!CachedClassHandles.TryGet(typeHandle, klass)) @@ -1911,6 +1917,7 @@ void* GetStaticMethodPointer(StringView methodName) if (CachedFunctions.TryGet(methodName, fun)) return fun; PROFILE_CPU(); + PROFILE_MEM(ScriptingCSharp); const int rc = get_function_pointer(NativeInteropTypeName, FLAX_CORECLR_STRING(methodName).Get(), UNMANAGEDCALLERSONLY_METHOD, nullptr, nullptr, &fun); if (rc != 0) LOG(Fatal, "Failed to get unmanaged function pointer for method '{0}': 0x{1:x}", methodName, (unsigned int)rc); @@ -2278,6 +2285,7 @@ void* GetStaticMethodPointer(StringView methodName) if (CachedFunctions.TryGet(methodName, fun)) return fun; PROFILE_CPU(); + PROFILE_MEM(ScriptingCSharp); static MonoClass* nativeInteropClass = nullptr; if (!nativeInteropClass) diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 86b800e97..06392f932 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -864,6 +864,11 @@ int32 MCore::GC::MaxGeneration() return mono_gc_max_generation(); } +void MCore::GC::MemoryInfo(int64& totalCommitted, int64& heapSize) +{ + totalCommitted = heapSize = 0; +} + void MCore::GC::WaitForPendingFinalizers() { PROFILE_CPU(); diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index 580029eef..1ddaeae8e 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -190,6 +190,11 @@ int32 MCore::GC::MaxGeneration() return 0; } +void MCore::GC::MemoryInfo(int64& totalCommitted, int64& heapSize) +{ + totalCommitted = heapSize = 0; +} + void MCore::GC::WaitForPendingFinalizers() { } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 8c27dc09f..3a69a7601 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -617,6 +617,7 @@ bool Scripting::Load() void Scripting::Release() { PROFILE_CPU(); + PROFILE_MEM(Scripting); // Note: this action can be called from main thread (due to Mono problems with assemblies actions from other threads) ASSERT(IsInMainThread()); From 03d52d4eb99f0daded2d28956c1fd1381972386d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 May 2025 04:05:12 +0200 Subject: [PATCH 19/84] Add support for building engine without logging --- Source/Editor/Analytics/EditorAnalytics.cpp | 4 ++ Source/Editor/Managed/ManagedEditor.cpp | 4 ++ Source/Engine/Content/Assets/VisualScript.cpp | 2 + Source/Engine/Core/Log.cpp | 3 ++ Source/Engine/Core/Log.h | 52 +++++++++++-------- Source/Engine/Core/LogContext.cpp | 2 + Source/Engine/Debug/Exception.cpp | 2 + Source/Engine/Engine/Engine.cpp | 8 +++ Source/Engine/Graphics/Graphics.cpp | 4 ++ .../Vulkan/RenderToolsVulkan.cpp | 2 + Source/Engine/Platform/Base/PlatformBase.cpp | 2 + Source/Engine/Platform/Mac/MacPlatform.cpp | 4 ++ Source/Engine/Profiler/ProfilerMemory.cpp | 2 + .../Internal/EngineInternalCalls.cpp | 4 ++ Source/Engine/Scripting/ManagedCLR/MCore.cpp | 2 + 15 files changed, 76 insertions(+), 21 deletions(-) diff --git a/Source/Editor/Analytics/EditorAnalytics.cpp b/Source/Editor/Analytics/EditorAnalytics.cpp index 385492789..ab4bf7f33 100644 --- a/Source/Editor/Analytics/EditorAnalytics.cpp +++ b/Source/Editor/Analytics/EditorAnalytics.cpp @@ -174,7 +174,9 @@ void EditorAnalytics::StartSession() // Bind events GameCooker::OnEvent.Bind(); ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Bind(); +#if LOG_ENABLE Log::Logger::OnError.Bind(); +#endif } void EditorAnalytics::EndSession() @@ -187,7 +189,9 @@ void EditorAnalytics::EndSession() // Unbind events GameCooker::OnEvent.Unbind(); ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Unbind(); +#if LOG_ENABLE Log::Logger::OnError.Unbind(); +#endif // End session { diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index d65ae6e0a..e270d08f8 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -156,7 +156,9 @@ ManagedEditor::ManagedEditor() lightmapsBuilder->OnBuildProgress.Bind(); lightmapsBuilder->OnBuildFinished.Bind(); CSG::Builder::OnBrushModified.Bind(); +#if LOG_ENABLE Log::Logger::OnMessage.Bind(); +#endif VisualScripting::DebugFlow.Bind(); } @@ -172,7 +174,9 @@ ManagedEditor::~ManagedEditor() lightmapsBuilder->OnBuildProgress.Unbind(); lightmapsBuilder->OnBuildFinished.Unbind(); CSG::Builder::OnBrushModified.Unbind(); +#if LOG_ENABLE Log::Logger::OnMessage.Unbind(); +#endif VisualScripting::DebugFlow.Unbind(); } diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 2e6ffb735..329696dea 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -38,10 +38,12 @@ namespace void PrintStack(LogType type) { +#if LOG_ENABLE const String stack = VisualScripting::GetStackTrace(); Log::Logger::Write(type, TEXT("Visual Script stack trace:")); Log::Logger::Write(type, stack); Log::Logger::Write(type, TEXT("")); +#endif } bool SerializeValue(const Variant& a, const Variant& b) diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index 85cc3cc02..013031ced 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -1,6 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. #include "Log.h" +#if LOG_ENABLE #include "Engine/Engine/CommandLine.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Collections/Array.h" @@ -310,3 +311,5 @@ const Char* ToString(LogType e) } return result; } + +#endif diff --git a/Source/Engine/Core/Log.h b/Source/Engine/Core/Log.h index 2db769e45..f09099d11 100644 --- a/Source/Engine/Core/Log.h +++ b/Source/Engine/Core/Log.h @@ -7,27 +7,6 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" -// Enable/disable auto flush function -#define LOG_ENABLE_AUTO_FLUSH 1 - -/// -/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum)) -/// -#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__)) - -/// -/// Sends a string message to the log file (message type - describes level of the log (see LogType enum)) -/// -#define LOG_STR(messageType, str) Log::Logger::Write(LogType::messageType, str) - -#if LOG_ENABLE_AUTO_FLUSH -// Noop as log is auto-flushed on write -#define LOG_FLUSH() -#else -// Flushes the log file buffer -#define LOG_FLUSH() Log::Logger::Flush() -#endif - /// /// The log message types. /// @@ -54,6 +33,29 @@ API_ENUM() enum class LogType Fatal = 8, }; +#if LOG_ENABLE + +// Enable/disable auto flush function +#define LOG_ENABLE_AUTO_FLUSH 1 + +/// +/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum)) +/// +#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__)) + +/// +/// Sends a string message to the log file (message type - describes level of the log (see LogType enum)) +/// +#define LOG_STR(messageType, str) Log::Logger::Write(LogType::messageType, str) + +#if LOG_ENABLE_AUTO_FLUSH +// Noop as log is auto-flushed on write +#define LOG_FLUSH() +#else +// Flushes the log file buffer +#define LOG_FLUSH() Log::Logger::Flush() +#endif + extern const Char* ToString(LogType e); namespace Log @@ -186,3 +188,11 @@ namespace Log static void ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w); }; } + +#else + +#define LOG(messageType, format, ...) +#define LOG_STR(messageType, str) +#define LOG_FLUSH() + +#endif diff --git a/Source/Engine/Core/LogContext.cpp b/Source/Engine/Core/LogContext.cpp index 8a14ad48c..5eeec5738 100644 --- a/Source/Engine/Core/LogContext.cpp +++ b/Source/Engine/Core/LogContext.cpp @@ -47,6 +47,7 @@ ThreadLocal GlobalLogContexts; void LogContext::Print(LogType verbosity) { +#if LOG_ENABLE auto& stack = GlobalLogContexts.Get(); if (stack.Count == 0) return; @@ -102,6 +103,7 @@ void LogContext::Print(LogType verbosity) // Print message Log::Logger::Write(verbosity, msg.ToStringView()); } +#endif } void LogContext::Push(const Guid& id) diff --git a/Source/Engine/Debug/Exception.cpp b/Source/Engine/Debug/Exception.cpp index d866d4867..7eb112aa1 100644 --- a/Source/Engine/Debug/Exception.cpp +++ b/Source/Engine/Debug/Exception.cpp @@ -4,6 +4,8 @@ Log::Exception::~Exception() { +#if LOG_ENABLE // Always write exception to the log Logger::Write(_level, ToString()); +#endif } diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 17a2f377b..a20c1780b 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -150,7 +150,9 @@ int32 Engine::Main(const Char* cmdLine) { // End LOG(Warning, "Loading project cancelled. Closing..."); +#if LOG_ENABLE Log::Logger::Dispose(); +#endif return 0; } #endif @@ -168,8 +170,10 @@ int32 Engine::Main(const Char* cmdLine) #if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC) EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground; #endif +#if LOG_ENABLE Log::Logger::WriteFloor(); LOG_FLUSH(); +#endif Time::Synchronize(); EngineImpl::IsReady = true; PROFILE_MEM_END(); @@ -546,14 +550,17 @@ void Engine::OnExit() ProfilerGPU::Dispose(); #endif +#if LOG_ENABLE // Close logging service Log::Logger::Dispose(); +#endif Platform::Exit(); } void EngineImpl::InitLog() { +#if LOG_ENABLE // Initialize logger Log::Logger::Init(); @@ -607,6 +614,7 @@ void EngineImpl::InitLog() Platform::LogInfo(); LOG_FLUSH(); +#endif } void EngineImpl::InitPaths() diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 43bd1a76d..733e8c222 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -101,8 +101,10 @@ bool GraphicsService::Init() PROFILE_MEM(Graphics); // Create and initialize graphics device +#if LOG_ENABLE Log::Logger::WriteFloor(); LOG(Info, "Creating Graphics Device..."); +#endif PixelFormatExtensions::Init(); GPUDevice* device = nullptr; @@ -216,7 +218,9 @@ bool GraphicsService::Init() { return true; } +#if LOG_ENABLE Log::Logger::WriteFloor(); +#endif return false; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp index 961799a42..d535f6ea1 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp @@ -250,8 +250,10 @@ void RenderToolsVulkan::LogVkResult(VkResult result, const char* file, uint32 li errorType = FatalErrorType::GPUCrash; if (errorType != FatalErrorType::None) Platform::Fatal(msg, nullptr, errorType); +#if LOG_ENABLE else Log::Logger::Write(fatal ? LogType::Fatal : LogType::Error, msg); +#endif } bool RenderToolsVulkan::HasExtension(const Array& extensions, const char* name) diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 9ba6b7bd6..7e8428e64 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -310,6 +310,7 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er Engine::RequestingExit(); // Collect crash info (platform-dependant implementation that might collect stack trace and/or create memory dump) +#if LOG_ENABLE { // Log separation for crash info LOG_FLUSH(); @@ -406,6 +407,7 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er LOG(Error, "Crash info collected."); Log::Logger::WriteFloor(); } +#endif // Show error message if (Engine::ReportCrash.IsBinded()) diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index fca66cf42..6cb8b1153 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -490,7 +490,9 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) StringView lineView(line); if (line[line.Length() - 1] == '\n') lineView = StringView(line.Get(), line.Length() - 1); +#if LOG_ENABLE Log::Logger::Write(LogType::Info, lineView); +#endif } [[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } @@ -517,7 +519,9 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) StringView lineView(line); if (line[line.Length() - 1] == '\n') lineView = StringView(line.Get(), line.Length() - 1); +#if LOG_ENABLE Log::Logger::Write(LogType::Error, lineView); +#endif } [[stderrPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index 61433c0e7..0aa2ba053 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -394,6 +394,7 @@ ProfilerMemory::GroupsArray ProfilerMemory::GetGroups(int32 mode) void ProfilerMemory::Dump(const StringView& options) { +#if LOG_ENABLE bool file = options.Contains(TEXT("file")); StringBuilder output; int32 maxCount = 20; @@ -408,6 +409,7 @@ void ProfilerMemory::Dump(const StringView& options) return; } LOG_STR(Info, output.ToStringView()); +#endif } void ProfilerMemory::OnMemoryAlloc(void* ptr, uint64 size) diff --git a/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp b/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp index 21323d2bd..a310409c0 100644 --- a/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp +++ b/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp @@ -52,13 +52,16 @@ DEFINE_INTERNAL_CALL(int32) PlatformInternal_MemoryCompare(const void* buf1, con DEFINE_INTERNAL_CALL(void) DebugLogHandlerInternal_LogWrite(LogType level, MString* msgObj) { +#if LOG_ENABLE StringView msg; MUtils::ToString(msgObj, msg); Log::Logger::Write(level, msg); +#endif } DEFINE_INTERNAL_CALL(void) DebugLogHandlerInternal_Log(LogType level, MString* msgObj, ScriptingObject* obj, MString* stackTrace) { +#if LOG_ENABLE if (msgObj == nullptr) return; @@ -71,6 +74,7 @@ DEFINE_INTERNAL_CALL(void) DebugLogHandlerInternal_Log(LogType level, MString* m // TODO: maybe option for build to threat warnings and errors as fatal errors? //const String logMessage = String::Format(TEXT("Debug:{1} {2}"), objName, *msg); Log::Logger::Write(level, msg); +#endif } DEFINE_INTERNAL_CALL(void) DebugLogHandlerInternal_LogException(MObject* exception, ScriptingObject* obj) diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 0b38730e8..4300434e3 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -246,6 +246,7 @@ MType* MEvent::GetType() const void MException::Log(const LogType type, const Char* target) { +#if LOG_ENABLE // Log inner exceptions chain MException* inner = InnerException; while (inner) @@ -260,6 +261,7 @@ void MException::Log(const LogType type, const Char* target) const String info = target && *target ? String::Format(TEXT("Exception has been thrown during {0}."), target) : TEXT("Exception has been thrown."); Log::Logger::Write(LogType::Warning, String::Format(TEXT("{0} {1}\nStack strace:\n{2}"), info, Message, stackTrace)); Log::Logger::Write(type, String::Format(TEXT("{0}\n{1}"), info, Message)); +#endif } MType* MProperty::GetType() const From 4fe9fdded67335d4112b29fcc836acd8b1043aba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 May 2025 04:10:47 +0200 Subject: [PATCH 20/84] Optimize redundant string allocation in managed binary module unload --- Source/Engine/Scripting/BinaryModule.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index ef76de61c..07feedf10 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1184,8 +1184,7 @@ void ManagedBinaryModule::OnUnloading(MAssembly* assembly) for (int32 i = _firstManagedTypeIndex; i < Types.Count(); i++) { const ScriptingType& type = Types[i]; - const StringAnsi typeName(type.Fullname.Get(), type.Fullname.Length()); - TypeNameToTypeIndex.Remove(typeName); + TypeNameToTypeIndex.Remove(type.Fullname); } } From 8eff098850f567a48c95a6c73751499a30f6b309 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 May 2025 04:30:08 +0200 Subject: [PATCH 21/84] Fix Linux build --- Source/Engine/Profiler/ProfilerMemory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index 0aa2ba053..dc91dbc02 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -163,7 +163,7 @@ namespace uint32 bit = (uint32)(1 << ((int32)group & 31)); if ((GroupTracyPlotEnable[(int32)group / 32] & bit) == bit) { - TracyPlot(GroupNames[(int32)group].Ansi, GroupMemory[(int32)group]); + TracyPlot(GroupNames[(int32)group].Ansi, (int64_t)GroupMemory[(int32)group]); } } #else From 0670c0bbd3c7bd092fc279599cbda606bc794e1d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 5 Jun 2025 18:32:36 +0200 Subject: [PATCH 22/84] Fix compilation warnings --- Source/Engine/Core/Memory/Allocation.cpp | 6 +++--- Source/Engine/Profiler/ProfilerMemory.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Core/Memory/Allocation.cpp b/Source/Engine/Core/Memory/Allocation.cpp index b2e74b8b6..c55ab1dab 100644 --- a/Source/Engine/Core/Memory/Allocation.cpp +++ b/Source/Engine/Core/Memory/Allocation.cpp @@ -11,7 +11,7 @@ void ArenaAllocator::Free() while (page) { #if COMPILE_WITH_PROFILER - ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -page->Size, -1); + ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1); #endif Allocator::Free(page->Memory); Page* next = page->Next; @@ -35,7 +35,7 @@ void* ArenaAllocator::Allocate(uint64 size, uint64 alignment) { uint64 pageSize = Math::Max(_pageSize, size); #if COMPILE_WITH_PROFILER - ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, pageSize, 1); + ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1); #endif page = (Page*)Allocator::Allocate(sizeof(Page)); page->Memory = Allocator::Allocate(pageSize); @@ -51,4 +51,4 @@ void* ArenaAllocator::Allocate(uint64 size, uint64 alignment) page->Offset += (uint32)size; return mem; -} \ No newline at end of file +} diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index dc91dbc02..ed1a825d5 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -35,7 +35,7 @@ struct GroupNameBuffer char prev = 0; for (int32 i = 0; i < max && dst < ARRAY_COUNT(Buffer) - 2; i++) { - char cur = str[i]; + char cur = (char)str[i]; if (autoFormat && StringUtils::IsUpper(cur) && StringUtils::IsLower(prev)) { Ansi[dst] = '/'; @@ -422,7 +422,7 @@ void ProfilerMemory::OnMemoryAlloc(void* ptr, uint64 size) // Register pointer PointerData ptrData; - ptrData.Size = size; + ptrData.Size = (uint32)size; ptrData.Group = (uint8)stack.Peek(); PointersLocker.Lock(); Pointers[ptr] = ptrData; From 9d8e75caa3d09902a12f27cd19665185c15c0cec Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Jun 2025 11:19:32 +0200 Subject: [PATCH 23/84] Fix various code to improve quality --- Source/Engine/Core/Log.h | 9 ++++++--- Source/Engine/Engine/Engine.cpp | 5 ++--- Source/Engine/Graphics/Graphics.cpp | 8 ++------ .../DirectX/DX11/GPUDeviceDX11.cpp | 2 +- .../DirectX/DX12/GPUContextDX12.cpp | 2 +- .../GraphicsDevice/DirectX/RenderToolsDX.cpp | 6 +++++- .../Vulkan/RenderToolsVulkan.cpp | 4 +++- Source/Engine/Platform/Base/PlatformBase.cpp | 6 +++--- .../Engine/Platform/Linux/LinuxPlatform.cpp | 2 ++ Source/Engine/Platform/Mac/MacPlatform.cpp | 8 ++++---- .../Platform/Windows/WindowsPlatform.cpp | 2 ++ Source/Engine/Profiler/ProfilerMemory.cpp | 20 +++++++++---------- Source/Engine/Tests/TestMain.cpp | 4 ++-- 13 files changed, 43 insertions(+), 35 deletions(-) diff --git a/Source/Engine/Core/Log.h b/Source/Engine/Core/Log.h index f09099d11..e10fc50a0 100644 --- a/Source/Engine/Core/Log.h +++ b/Source/Engine/Core/Log.h @@ -56,6 +56,8 @@ API_ENUM() enum class LogType #define LOG_FLUSH() Log::Logger::Flush() #endif +#define LOG_FLOOR() Log::Logger::WriteFloor() + extern const Char* ToString(LogType e); namespace Log @@ -191,8 +193,9 @@ namespace Log #else -#define LOG(messageType, format, ...) -#define LOG_STR(messageType, str) -#define LOG_FLUSH() +#define LOG(messageType, format, ...) {} +#define LOG_STR(messageType, str) {} +#define LOG_FLUSH() {} +#define LOG_FLOOR() {} #endif diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index d9a4173c8..5607432c2 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -170,10 +170,8 @@ int32 Engine::Main(const Char* cmdLine) #if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC) EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground; #endif -#if LOG_ENABLE - Log::Logger::WriteFloor(); + LOG_FLOOR(); LOG_FLUSH(); -#endif Time::Synchronize(); EngineImpl::IsReady = true; PROFILE_MEM_END(); @@ -557,6 +555,7 @@ void Engine::OnExit() #if COMPILE_WITH_PROFILER ProfilerCPU::Dispose(); ProfilerGPU::Dispose(); + ProfilerMemory::Enabled = false; #endif #if LOG_ENABLE diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 733e8c222..bf17970ea 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -101,10 +101,8 @@ bool GraphicsService::Init() PROFILE_MEM(Graphics); // Create and initialize graphics device -#if LOG_ENABLE - Log::Logger::WriteFloor(); + LOG_FLOOR(); LOG(Info, "Creating Graphics Device..."); -#endif PixelFormatExtensions::Init(); GPUDevice* device = nullptr; @@ -218,9 +216,7 @@ bool GraphicsService::Init() { return true; } -#if LOG_ENABLE - Log::Logger::WriteFloor(); -#endif + LOG_FLOOR(); return false; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index c578fd295..411d9dd92 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -765,7 +765,7 @@ void GPUDeviceDX11::DrawEnd() { GPUDeviceDX::DrawEnd(); -#if GPU_ENABLE_DIAGNOSTICS +#if GPU_ENABLE_DIAGNOSTICS && LOG_ENABLE // Flush debug messages queue ComPtr infoQueue; VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index dd2bc3da4..5f278a8ae 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -39,7 +39,7 @@ #include "Engine/Threading/Threading.h" #define DX12_ENABLE_RESOURCE_BARRIERS_BATCHING 1 -#define DX12_ENABLE_RESOURCE_BARRIERS_DEBUGGING 0 +#define DX12_ENABLE_RESOURCE_BARRIERS_DEBUGGING (0 && LOG_ENABLE) inline bool operator!=(const D3D12_VERTEX_BUFFER_VIEW& l, const D3D12_VERTEX_BUFFER_VIEW& r) { diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp index f4bf4a3df..f195b8b41 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp @@ -387,10 +387,14 @@ void RenderToolsDX::LogD3DResult(HRESULT result, const char* file, uint32 line, if (removedReason == DXGI_ERROR_DEVICE_HUNG) errorType = FatalErrorType::GPUHang; } + else if (fatal) + errorType = FatalErrorType::Unknown; if (errorType != FatalErrorType::None) Platform::Fatal(msg, nullptr, errorType); +#if LOG_ENABLE else - Log::Logger::Write(fatal ? LogType::Fatal : LogType::Error, msg); + Log::Logger::Write(LogType::Error, msg); +#endif } LPCSTR RenderToolsDX::GetVertexInputSemantic(VertexElement::Types type, UINT& semanticIndex) diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp index d535f6ea1..604b8a612 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp @@ -248,11 +248,13 @@ void RenderToolsVulkan::LogVkResult(VkResult result, const char* file, uint32 li errorType = FatalErrorType::GPUHang; else if (result == VK_ERROR_DEVICE_LOST || result == VK_ERROR_SURFACE_LOST_KHR || result == VK_ERROR_MEMORY_MAP_FAILED) errorType = FatalErrorType::GPUCrash; + else if (fatal) + errorType = FatalErrorType::Unknown; if (errorType != FatalErrorType::None) Platform::Fatal(msg, nullptr, errorType); #if LOG_ENABLE else - Log::Logger::Write(fatal ? LogType::Fatal : LogType::Error, msg); + Log::Logger::Write(LogType::Error, msg); #endif } diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 7e8428e64..0bde861c7 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -314,7 +314,7 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er { // Log separation for crash info LOG_FLUSH(); - Log::Logger::WriteFloor(); + LOG_FLOOR(); LOG(Error, ""); LOG(Error, "Critical error! Reason: {0}", msg); LOG(Error, ""); @@ -400,12 +400,12 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er // Capture the original log file LOG(Error, ""); - Log::Logger::WriteFloor(); + LOG_FLOOR(); LOG_FLUSH(); FileSystem::CopyFile(crashDataFolder / TEXT("Log.txt"), Log::Logger::LogFilePath); LOG(Error, "Crash info collected."); - Log::Logger::WriteFloor(); + LOG_FLOOR(); } #endif diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 44e5a313d..4b55f8bd5 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -3029,8 +3029,10 @@ int32 LinuxPlatform::CreateProcess(CreateProcessSettings& settings) String line(lineBuffer); if (settings.SaveOutput) settings.Output.Add(line.Get(), line.Length()); +#if LOG_ENABLE if (settings.LogOutput) Log::Logger::Write(LogType::Info, line); +#endif } } int stat_loc; diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 6cb8b1153..6c7fbf533 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -485,15 +485,15 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) String line((const char*)data.bytes, data.length); if (settings.SaveOutput) settings.Output.Add(line.Get(), line.Length()); +#if LOG_ENABLE if (settings.LogOutput) { StringView lineView(line); if (line[line.Length() - 1] == '\n') lineView = StringView(line.Get(), line.Length() - 1); -#if LOG_ENABLE Log::Logger::Write(LogType::Info, lineView); -#endif } +#endif [[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } } @@ -514,15 +514,15 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) String line((const char*)data.bytes, data.length); if (settings.SaveOutput) settings.Output.Add(line.Get(), line.Length()); +#if LOG_ENABLE if (settings.LogOutput) { StringView lineView(line); if (line[line.Length() - 1] == '\n') lineView = StringView(line.Get(), line.Length() - 1); -#if LOG_ENABLE Log::Logger::Write(LogType::Error, lineView); -#endif } +#endif [[stderrPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } } diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 277927af3..697174a47 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -1054,8 +1054,10 @@ void ReadPipe(HANDLE pipe, Array& rawData, Array& logData, LogType l int32 tmp; StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count(), tmp); logData.Last() = '\0'; +#if LOG_ENABLE if (settings.LogOutput) Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count())); +#endif if (settings.SaveOutput) settings.Output.Add(logData.Get(), rawData.Count()); } diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index ed1a825d5..972dd6646 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -310,12 +310,12 @@ void TickProfilerMemory() // Update profiler memory PointersLocker.Lock(); - GroupMemory[(int32)ProfilerMemory::Groups::Profiler] = - sizeof(GroupMemory) + sizeof(GroupNames) + sizeof(GroupStack) + + GroupMemory[(int32)ProfilerMemory::Groups::Profiler] = + sizeof(GroupMemory) + sizeof(GroupNames) + sizeof(GroupStack) + #ifdef USE_TRACY_MEMORY_PLOTS - sizeof(GroupTracyPlotEnable) + + sizeof(GroupTracyPlotEnable) + #endif - Pointers.Capacity() * sizeof(Dictionary::Bucket); + Pointers.Capacity() * sizeof(Dictionary::Bucket); PointersLocker.Unlock(); // Get total system memory and update untracked amount @@ -431,8 +431,8 @@ void ProfilerMemory::OnMemoryAlloc(void* ptr, uint64 size) // 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]); + Platform::InterlockedAdd(&GroupMemory[(int32)Groups::Malloc], add); + Platform::InterlockedIncrement(&GroupMemoryCount[(int32)Groups::Malloc]); UPDATE_PEEK(ProfilerMemory::Groups::Malloc); stack.SkipRecursion = false; @@ -453,16 +453,16 @@ void ProfilerMemory::OnMemoryFree(void* ptr) bool found = it.IsNotEnd(); if (found) ptrData = it->Value; - Pointers.Remove(it); - PointersLocker.Unlock(); + 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]); + Platform::InterlockedAdd(&GroupMemory[(int32)Groups::Malloc], add); + Platform::InterlockedDecrement(&GroupMemoryCount[(int32)Groups::Malloc]); } stack.SkipRecursion = false; diff --git a/Source/Engine/Tests/TestMain.cpp b/Source/Engine/Tests/TestMain.cpp index 88db6144c..061f1343b 100644 --- a/Source/Engine/Tests/TestMain.cpp +++ b/Source/Engine/Tests/TestMain.cpp @@ -40,14 +40,14 @@ void TestsRunnerService::Update() return; // Runs tests - Log::Logger::WriteFloor(); + LOG_FLOOR(); LOG(Info, "Running Flax Tests..."); const int result = Catch::Session().run(); if (result == 0) LOG(Info, "Flax Tests result: {0}", result); else LOG(Error, "Flax Tests result: {0}", result); - Log::Logger::WriteFloor(); + LOG_FLOOR(); Engine::RequestExit(result); } From cd637e8a7afd5ff1c92c246759fdd2df6f79a619 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Jun 2025 14:38:22 +0200 Subject: [PATCH 24/84] Add more memory profiling coverage --- Source/Engine/Content/Storage/FlaxStorage.cpp | 1 + Source/Engine/Core/Delegate.h | 4 ++++ Source/Engine/Graphics/Textures/GPUTexture.cpp | 3 +++ Source/Engine/Input/Input.cpp | 9 +++++++++ Source/Engine/Profiler/ProfilerCPU.cpp | 2 ++ Source/Engine/Profiler/ProfilerGPU.cpp | 4 ++++ Source/Engine/Profiler/ProfilerMemory.cpp | 2 ++ Source/Engine/Profiler/ProfilerMemory.h | 9 +++++++-- Source/Engine/Threading/ConcurrentQueue.h | 2 ++ Source/Engine/Threading/JobSystem.cpp | 6 ++++++ Source/Engine/Threading/ThreadPool.cpp | 4 ++++ Source/Engine/Threading/ThreadRegistry.cpp | 4 +++- Source/Engine/Tools/TextureTool/TextureTool.cpp | 7 +++++++ Source/Engine/Utilities/Screenshot.cpp | 3 +++ 14 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 17daba278..ed8b623ae 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -1425,6 +1425,7 @@ bool FlaxStorage::CloseFileHandles() return false; } PROFILE_CPU(); + PROFILE_MEM(ContentFiles); // Note: this is usually called by the content manager when this file is not used or on exit // In those situations all the async tasks using this storage should be cancelled externally diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index b11d9b845..8efc2beec 100644 --- a/Source/Engine/Core/Delegate.h +++ b/Source/Engine/Core/Delegate.h @@ -12,6 +12,9 @@ #include "Engine/Threading/Threading.h" #include "Engine/Core/Collections/HashSet.h" #endif +#if COMPILE_WITH_PROFILER +#include "Engine/Profiler/ProfilerMemory.h" +#endif /// /// The function object that supports binding static, member and lambda functions. @@ -457,6 +460,7 @@ public: /// The function to bind. void Bind(const FunctionType& f) { + PROFILE_MEM(EngineDelegate); #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead(&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 245fb1e01..07b85bc42 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -628,6 +628,7 @@ GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipInde GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData) { PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); ASSERT(IsAllocated()); ASSERT(mipIndex < MipLevels() && data.IsValid()); ASSERT(data.Length() >= slicePitch); @@ -720,6 +721,7 @@ bool GPUTexture::DownloadData(TextureData& result) MISSING_CODE("support volume texture data downloading."); } PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); // Use faster path for staging resources if (IsStaging()) // TODO: what about chips with unified memory? if rendering is not active then we can access GPU memory from CPU directly (eg. mobile, integrated GPUs and some consoles) @@ -806,6 +808,7 @@ Task* GPUTexture::DownloadDataAsync(TextureData& result) MISSING_CODE("support volume texture data downloading."); } PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); // Use faster path for staging resources if (IsStaging()) diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index e0fc0297f..8438977b1 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -169,6 +169,7 @@ void Mouse::OnMouseMoved(const Float2& newPosition) void Mouse::OnMouseDown(const Float2& position, const MouseButton button, Window* target) { + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::MouseDown; e.Target = target; @@ -187,6 +188,7 @@ bool Mouse::IsAnyButtonDown() const void Mouse::OnMouseUp(const Float2& position, const MouseButton button, Window* target) { + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::MouseUp; e.Target = target; @@ -196,6 +198,7 @@ void Mouse::OnMouseUp(const Float2& position, const MouseButton button, Window* void Mouse::OnMouseDoubleClick(const Float2& position, const MouseButton button, Window* target) { + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::MouseDoubleClick; e.Target = target; @@ -205,6 +208,7 @@ void Mouse::OnMouseDoubleClick(const Float2& position, const MouseButton button, void Mouse::OnMouseMove(const Float2& position, Window* target) { + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::MouseMove; e.Target = target; @@ -213,6 +217,7 @@ void Mouse::OnMouseMove(const Float2& position, Window* target) void Mouse::OnMouseLeave(Window* target) { + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::MouseLeave; e.Target = target; @@ -220,6 +225,7 @@ void Mouse::OnMouseLeave(Window* target) void Mouse::OnMouseWheel(const Float2& position, float delta, Window* target) { + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::MouseWheel; e.Target = target; @@ -316,6 +322,7 @@ void Keyboard::OnCharInput(Char c, Window* target) if (c < 32) return; + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::Char; e.Target = target; @@ -326,6 +333,7 @@ void Keyboard::OnKeyUp(KeyboardKeys key, Window* target) { if (key >= KeyboardKeys::MAX) return; + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::KeyUp; e.Target = target; @@ -336,6 +344,7 @@ void Keyboard::OnKeyDown(KeyboardKeys key, Window* target) { if (key >= KeyboardKeys::MAX) return; + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::KeyDown; e.Target = target; diff --git a/Source/Engine/Profiler/ProfilerCPU.cpp b/Source/Engine/Profiler/ProfilerCPU.cpp index 9ebc39bd5..5f5141e9e 100644 --- a/Source/Engine/Profiler/ProfilerCPU.cpp +++ b/Source/Engine/Profiler/ProfilerCPU.cpp @@ -3,6 +3,7 @@ #if COMPILE_WITH_PROFILER #include "ProfilerCPU.h" +#include "ProfilerMemory.h" #include "Engine/Engine/Globals.h" #include "Engine/Threading/ThreadRegistry.h" @@ -157,6 +158,7 @@ int32 ProfilerCPU::BeginEvent() auto thread = Thread::Current; if (thread == nullptr) { + PROFILE_MEM(Profiler); const auto id = Platform::GetCurrentThreadID(); const auto t = ThreadRegistry::GetThread(id); if (t) diff --git a/Source/Engine/Profiler/ProfilerGPU.cpp b/Source/Engine/Profiler/ProfilerGPU.cpp index 168677c1b..9330663f4 100644 --- a/Source/Engine/Profiler/ProfilerGPU.cpp +++ b/Source/Engine/Profiler/ProfilerGPU.cpp @@ -3,6 +3,7 @@ #if COMPILE_WITH_PROFILER #include "ProfilerGPU.h" +#include "ProfilerMemory.h" #include "Engine/Core/Log.h" #include "Engine/Engine/Engine.h" #include "Engine/Graphics/GPUDevice.h" @@ -45,6 +46,7 @@ void ProfilerGPU::EventBuffer::TryResolve() } // Collect queries results and free them + PROFILE_MEM(Profiler); for (int32 i = 0; i < _data.Count(); i++) { auto& e = _data[i]; @@ -58,6 +60,7 @@ void ProfilerGPU::EventBuffer::TryResolve() int32 ProfilerGPU::EventBuffer::Add(const Event& e) { + PROFILE_MEM(Profiler); const int32 index = _data.Count(); _data.Add(e); return index; @@ -88,6 +91,7 @@ GPUTimerQuery* ProfilerGPU::GetTimerQuery() } else { + PROFILE_MEM(Profiler); result = GPUDevice::Instance->CreateTimerQuery(); _timerQueriesPool.Add(result); } diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index 972dd6646..807f397cc 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -234,6 +234,8 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage) // Init hierarchy #define INIT_PARENT(parent, child) GroupParents[(int32)ProfilerMemory::Groups::child] = (uint8)ProfilerMemory::Groups::parent + INIT_PARENT(Engine, EngineThreading); + INIT_PARENT(Engine, EngineDelegate); INIT_PARENT(Malloc, MallocArena); INIT_PARENT(Graphics, GraphicsTextures); INIT_PARENT(Graphics, GraphicsRenderTargets); diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h index 1b1ad5ac3..2112a407d 100644 --- a/Source/Engine/Profiler/ProfilerMemory.h +++ b/Source/Engine/Profiler/ProfilerMemory.h @@ -30,8 +30,6 @@ public: TotalUntracked, // Initial memory used by program upon startup (eg. executable size, static variables). ProgramSize, - // General purpose engine memory. - Engine, // Profiling tool memory overhead. Profiler, @@ -40,6 +38,13 @@ public: // Total memory allocated via arena allocators (all pages). MallocArena, + // General purpose engine memory. + Engine, + // Memory used by the threads (and relevant systems such as Job System). + EngineThreading, + // Memory used by Delegate (engine events system to store all references). + EngineDelegate, + // Total graphics memory usage. Graphics, // Total textures memory usage. diff --git a/Source/Engine/Threading/ConcurrentQueue.h b/Source/Engine/Threading/ConcurrentQueue.h index ba60dcec6..79c819cc8 100644 --- a/Source/Engine/Threading/ConcurrentQueue.h +++ b/Source/Engine/Threading/ConcurrentQueue.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Memory/Memory.h" +#include "Engine/Profiler/ProfilerMemory.h" #define MOODYCAMEL_EXCEPTIONS_ENABLED 0 #include @@ -17,6 +18,7 @@ struct ConcurrentQueueSettings : public moodycamel::ConcurrentQueueDefaultTraits // Use default engine memory allocator static inline void* malloc(size_t size) { + PROFILE_MEM(EngineThreading); return Allocator::Allocate((uint64)size); } diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index 634f1a884..612584c40 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -12,6 +12,7 @@ #include "Engine/Core/Collections/RingBuffer.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #if USE_CSHARP #include "Engine/Scripting/ManagedCLR/MCore.h" #endif @@ -118,17 +119,22 @@ void* JobSystemAllocation::Allocate(uintptr size) } } if (!result) + { + PROFILE_MEM(EngineThreading); result = Platform::Allocate(size, 16); + } return result; } void JobSystemAllocation::Free(void* ptr, uintptr size) { + PROFILE_MEM(EngineThreading); MemPool.Add({ ptr, size }); } bool JobSystemService::Init() { + PROFILE_MEM(EngineThreading); ThreadsCount = Math::Min(Platform::GetCPUInfo().LogicalProcessorCount, ARRAY_COUNT(Threads)); for (int32 i = 0; i < ThreadsCount; i++) { diff --git a/Source/Engine/Threading/ThreadPool.cpp b/Source/Engine/Threading/ThreadPool.cpp index aeb4dea98..e84aa2cdd 100644 --- a/Source/Engine/Threading/ThreadPool.cpp +++ b/Source/Engine/Threading/ThreadPool.cpp @@ -14,6 +14,7 @@ #include "Engine/Platform/ConditionVariable.h" #include "Engine/Platform/CPUInfo.h" #include "Engine/Platform/Thread.h" +#include "Engine/Profiler/ProfilerMemory.h" FLAXENGINE_API bool IsInMainThread() { @@ -36,6 +37,7 @@ String ThreadPoolTask::ToString() const void ThreadPoolTask::Enqueue() { + PROFILE_MEM(EngineThreading); ThreadPoolImpl::Jobs.Add(this); ThreadPoolImpl::JobsSignal.NotifyOne(); } @@ -58,6 +60,8 @@ ThreadPoolService ThreadPoolServiceInstance; bool ThreadPoolService::Init() { + PROFILE_MEM(EngineThreading); + // Spawn threads const int32 numThreads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 2, PLATFORM_THREADS_LIMIT / 2); LOG(Info, "Spawning {0} Thread Pool workers", numThreads); diff --git a/Source/Engine/Threading/ThreadRegistry.cpp b/Source/Engine/Threading/ThreadRegistry.cpp index d793f7e56..522a5479b 100644 --- a/Source/Engine/Threading/ThreadRegistry.cpp +++ b/Source/Engine/Threading/ThreadRegistry.cpp @@ -3,10 +3,11 @@ #include "ThreadRegistry.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Platform/CriticalSection.h" +#include "Engine/Profiler/ProfilerMemory.h" namespace ThreadRegistryImpl { - Dictionary Registry(64); + Dictionary Registry; CriticalSection Locker; } @@ -46,6 +47,7 @@ void ThreadRegistry::KillEmAll() void ThreadRegistry::Add(Thread* thread) { + PROFILE_MEM(EngineThreading); ASSERT(thread && thread->GetID() != 0); Locker.Lock(); ASSERT(!Registry.ContainsKey(thread->GetID()) && !Registry.ContainsValue(thread)); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index 75f4da774..b892385da 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -14,6 +14,7 @@ #include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #if USE_EDITOR #include "Engine/Core/Collections/Dictionary.h" @@ -210,6 +211,7 @@ bool TextureTool::HasAlpha(const StringView& path) bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData) { PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); LOG(Info, "Importing texture from \'{0}\'", path); const auto startTime = DateTime::NowUTC(); @@ -247,6 +249,7 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData, Options options, String& errorMsg) { PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); LOG(Info, "Importing texture from \'{0}\'. Options: {1}", path, options.ToString()); const auto startTime = DateTime::NowUTC(); @@ -296,6 +299,7 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData bool TextureTool::ExportTexture(const StringView& path, const TextureData& textureData) { PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); LOG(Info, "Exporting texture to \'{0}\'.", path); const auto startTime = DateTime::NowUTC(); ImageType type; @@ -346,6 +350,7 @@ bool TextureTool::Convert(TextureData& dst, const TextureData& src, const PixelF return true; } PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); #if COMPILE_WITH_DIRECTXTEX return ConvertDirectXTex(dst, src, dstFormat); @@ -375,6 +380,7 @@ bool TextureTool::Resize(TextureData& dst, const TextureData& src, int32 dstWidt return true; } PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); #if COMPILE_WITH_DIRECTXTEX return ResizeDirectXTex(dst, src, dstWidth, dstHeight); #elif COMPILE_WITH_STB @@ -488,6 +494,7 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type) bool TextureTool::Transform(TextureData& texture, const Function& transformation) { PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); auto sampler = PixelFormatSampler::Get(texture.Format); if (!sampler) return true; diff --git a/Source/Engine/Utilities/Screenshot.cpp b/Source/Engine/Utilities/Screenshot.cpp index 85abf19d8..9a453701d 100644 --- a/Source/Engine/Utilities/Screenshot.cpp +++ b/Source/Engine/Utilities/Screenshot.cpp @@ -82,6 +82,7 @@ bool CaptureScreenshot::Run() LOG(Warning, "Missing target render task."); return true; } + PROFILE_MEM(Graphics); // TODO: how about a case two or more screenshots at the same second? update counter and check files @@ -147,6 +148,7 @@ void Screenshot::Capture(GPUTexture* target, const StringView& path) LOG(Warning, "Cannot take screenshot. Graphics device is not ready."); return; } + PROFILE_MEM(Graphics); // Faster path for staging textures that contents are ready to access on a CPU if (target->IsStaging()) @@ -211,6 +213,7 @@ void Screenshot::Capture(SceneRenderTask* target, const StringView& path) LOG(Warning, "Cannot take screenshot. Graphics device is not ready."); return; } + PROFILE_MEM(Graphics); // Create tasks auto saveTask = New(target, path); From e8b60060ab5601e38e38510da8891c9faeacbbb1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Jun 2025 14:52:27 +0200 Subject: [PATCH 25/84] Fix memory profiler thread-local storage to avoid dynamic mem alloc due to recursive call --- Source/Engine/Profiler/ProfilerMemory.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index 807f397cc..49cc9649f 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -106,7 +106,13 @@ namespace alignas(16) volatile uint32 GroupTracyPlotEnable[(GROUPS_COUNT + 31) / 32] = {}; #endif uint8 GroupParents[GROUPS_COUNT] = {}; +#if 0 ThreadLocal GroupStack; +#define GetGroupStack() GroupStack.Get(); +#else + THREADLOCAL GroupStackData GroupStack; +#define GetGroupStack() GroupStack +#endif GroupNameBuffer GroupNames[GROUPS_COUNT]; CriticalSection PointersLocker; Dictionary Pointers; @@ -347,13 +353,13 @@ void ProfilerMemory::DecrementGroup(Groups group, uint64 size) void ProfilerMemory::BeginGroup(Groups group) { - auto& stack = GroupStack.Get(); + auto& stack = GetGroupStack(); stack.Push(group); } void ProfilerMemory::EndGroup() { - auto& stack = GroupStack.Get(); + auto& stack = GetGroupStack(); stack.Pop(); } @@ -417,7 +423,7 @@ void ProfilerMemory::Dump(const StringView& options) void ProfilerMemory::OnMemoryAlloc(void* ptr, uint64 size) { ASSERT_LOW_LAYER(Enabled && ptr); - auto& stack = GroupStack.Get(); + auto& stack = GetGroupStack(); if (stack.SkipRecursion) return; stack.SkipRecursion = true; @@ -443,7 +449,7 @@ void ProfilerMemory::OnMemoryAlloc(void* ptr, uint64 size) void ProfilerMemory::OnMemoryFree(void* ptr) { ASSERT_LOW_LAYER(Enabled && ptr); - auto& stack = GroupStack.Get(); + auto& stack = GetGroupStack(); if (stack.SkipRecursion) return; stack.SkipRecursion = true; From 091f76bbf29b2bdf429e9b83c35d45c2d3197a75 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Jun 2025 22:40:43 +0200 Subject: [PATCH 26/84] Add more improvements to usability of memory profiler --- Source/Editor/Windows/Profiler/Memory.cs | 2 +- Source/Engine/Foliage/Foliage.cpp | 6 ++++++ Source/Engine/Foliage/FoliageType.cpp | 5 +++++ Source/Engine/Profiler/ProfilerMemory.cpp | 19 +++++++++++-------- Source/Engine/Profiler/ProfilerMemory.h | 20 ++++++++++++-------- Source/Engine/Terrain/Terrain.cpp | 6 ++++++ Source/Engine/Terrain/TerrainManager.cpp | 8 +++++--- Source/Engine/Terrain/TerrainPatch.cpp | 17 +++++++++++++++++ 8 files changed, 63 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index 806bed18f..6958b828b 100644 --- a/Source/Editor/Windows/Profiler/Memory.cs +++ b/Source/Editor/Windows/Profiler/Memory.cs @@ -232,7 +232,7 @@ namespace FlaxEditor.Windows.Profiler Array.Sort(_groupOrder, (x, y) => { var tmp = _frames.Get(selectedFrame); - return (int)(tmp.Usage.Values0[y] - tmp.Usage.Values0[x]); + return tmp.Usage.Values0[y].CompareTo(tmp.Usage.Values0[x]); }); // Add rows diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index a1cd046ae..29771c29c 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -400,6 +400,7 @@ void Foliage::DrawClusterGlobalSA(GlobalSurfaceAtlasPass* globalSA, const Vector void Foliage::DrawFoliageJob(int32 i) { PROFILE_CPU(); + PROFILE_MEM(Graphics); const FoliageType& type = FoliageTypes[i]; if (type.IsReady() && type.Model->CanBeRendered()) { @@ -551,6 +552,7 @@ FoliageType* Foliage::GetFoliageType(int32 index) void Foliage::AddFoliageType(Model* model) { PROFILE_CPU(); + PROFILE_MEM(LevelFoliage); // Ensure to have unique model CHECK(model); @@ -629,6 +631,7 @@ int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const void Foliage::AddInstance(const FoliageInstance& instance) { + PROFILE_MEM(LevelFoliage); ASSERT(instance.Type >= 0 && instance.Type < FoliageTypes.Count()); auto type = &FoliageTypes[instance.Type]; @@ -705,6 +708,7 @@ void Foliage::OnFoliageTypeModelLoaded(int32 index) if (_disableFoliageTypeEvents) return; PROFILE_CPU(); + PROFILE_MEM(LevelFoliage); auto& type = FoliageTypes[index]; ASSERT(type.IsReady()); @@ -803,6 +807,7 @@ void Foliage::OnFoliageTypeModelLoaded(int32 index) void Foliage::RebuildClusters() { PROFILE_CPU(); + PROFILE_MEM(LevelFoliage); // Faster path if foliage is empty or no types is ready bool anyTypeReady = false; @@ -1328,6 +1333,7 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie Actor::Deserialize(stream, modifier); PROFILE_CPU(); + PROFILE_MEM(LevelFoliage); // Clear #if FOLIAGE_USE_SINGLE_QUAD_TREE diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index 0f1893e67..8b8c84420 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -4,6 +4,7 @@ #include "Engine/Core/Collections/ArrayExtensions.h" #include "Engine/Core/Random.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Foliage.h" FoliageType::FoliageType() @@ -62,6 +63,7 @@ Array FoliageType::GetMaterials() const void FoliageType::SetMaterials(const Array& value) { + PROFILE_MEM(LevelFoliage); CHECK(value.Count() == Entries.Count()); for (int32 i = 0; i < value.Count(); i++) Entries[i].Material = value[i]; @@ -114,6 +116,8 @@ void FoliageType::OnModelChanged() void FoliageType::OnModelLoaded() { + PROFILE_MEM(LevelFoliage); + // Now it's ready _isReady = 1; @@ -169,6 +173,7 @@ void FoliageType::Serialize(SerializeStream& stream, const void* otherObj) void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { + PROFILE_MEM(LevelFoliage); DESERIALIZE(Model); const auto member = stream.FindMember("Materials"); diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index 49cc9649f..d53e48b17 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -21,6 +21,7 @@ #define USE_TRACY_MEMORY_PLOTS (defined(TRACY_ENABLE)) static_assert(GROUPS_COUNT <= MAX_uint8, "Fix memory profiler groups to fit a single byte."); +static_assert(sizeof(ProfilerMemory::Groups) == sizeof(uint8), "Fix memory profiler groups to fit a single byte."); // Compact name storage. struct GroupNameBuffer @@ -32,17 +33,17 @@ struct GroupNameBuffer void Set(const T* str, bool autoFormat = false) { int32 max = StringUtils::Length(str), dst = 0; - char prev = 0; + T prev = 0; for (int32 i = 0; i < max && dst < ARRAY_COUNT(Buffer) - 2; i++) { - char cur = (char)str[i]; + T cur = (T)str[i]; if (autoFormat && StringUtils::IsUpper(cur) && StringUtils::IsLower(prev)) { Ansi[dst] = '/'; Buffer[dst++] = '/'; } - Ansi[dst] = cur; - Buffer[dst++] = cur; + Ansi[dst] = (char)cur; + Buffer[dst++] = (Char)cur; prev = cur; } Buffer[dst] = 0; @@ -257,6 +258,8 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage) INIT_PARENT(Animations, AnimationsData); INIT_PARENT(Content, ContentAssets); INIT_PARENT(Content, ContentFiles); + INIT_PARENT(Level, LevelFoliage); + INIT_PARENT(Level, LevelTerrain); INIT_PARENT(Scripting, ScriptingVisual); INIT_PARENT(Scripting, ScriptingCSharp); INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCCommitted); @@ -403,10 +406,10 @@ ProfilerMemory::GroupsArray ProfilerMemory::GetGroups(int32 mode) void ProfilerMemory::Dump(const StringView& options) { #if LOG_ENABLE - bool file = options.Contains(TEXT("file")); + bool file = options.Contains(TEXT("file"), StringSearchCase::IgnoreCase); StringBuilder output; int32 maxCount = 20; - if (file || options.Contains(TEXT("all"))) + if (file || options.Contains(TEXT("all"), StringSearchCase::IgnoreCase)) maxCount = MAX_int32; ::Dump(output, maxCount); if (file) @@ -476,10 +479,10 @@ void ProfilerMemory::OnMemoryFree(void* ptr) stack.SkipRecursion = false; } -void ProfilerMemory::OnGroupUpdate(Groups group, int64 sizeDelta, int64 countDetla) +void ProfilerMemory::OnGroupUpdate(Groups group, int64 sizeDelta, int64 countDelta) { Platform::InterlockedAdd(&GroupMemory[(int32)group], sizeDelta); - Platform::InterlockedAdd(&GroupMemoryCount[(int32)group], countDetla); + Platform::InterlockedAdd(&GroupMemoryCount[(int32)group], countDelta); UPDATE_PEEK(group); } diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h index 2112a407d..e42b8720e 100644 --- a/Source/Engine/Profiler/ProfilerMemory.h +++ b/Source/Engine/Profiler/ProfilerMemory.h @@ -61,7 +61,7 @@ public: GraphicsVertexBuffers, // Total index buffers memory usage. GraphicsIndexBuffers, - // Total meshes memory usage (vertex and idnex buffers allocated by models). + // Total meshes memory usage (vertex and index buffers allocated by models). GraphicsMeshes, // Totoal shaders memory usage (shaders bytecode, PSOs data). GraphicsShaders, @@ -78,7 +78,7 @@ public: // Total animation data memory usage (curves, events, keyframes, graphs, etc.). AnimationsData, - // Total autio system memory. + // Total audio system memory. Audio, // Total content system memory usage. @@ -90,11 +90,15 @@ public: // Total memory used by content streaming system (internals). ContentStreaming, - // Total memory allocated by input system. - Input, - // Total memory allocated by scene objects. Level, + // Total memory allocated by the foliage system (quad-tree, foliage instances data). Excluding foliage models data. + LevelFoliage, + // Total memory allocated by the terrain system (patches). + LevelTerrain, + + // Total memory allocated by input system. + Input, // Total localization system memory. Localization, @@ -148,7 +152,7 @@ public: CustomGame8, // Custom game-specific memory tracking. CustomGame9, - + // Custom plugin-specific memory tracking. CustomPlugin0, // Custom plugin-specific memory tracking. @@ -186,7 +190,7 @@ public: }; /// - /// The memory groups array wraper to avoid dynamic memory allocation. + /// The memory groups array wrapper to avoid dynamic memory allocation. /// API_STRUCT(NoDefault) struct GroupsArray { @@ -254,7 +258,7 @@ public: static void OnMemoryAlloc(void* ptr, uint64 size); static void OnMemoryFree(void* ptr); - static void OnGroupUpdate(Groups group, int64 sizeDelta, int64 countDetla); + static void OnGroupUpdate(Groups group, int64 sizeDelta, int64 countDelta); public: /// diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index ebbfd70e6..85274b8fb 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -16,6 +16,7 @@ #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Renderer/GlobalSignDistanceFieldPass.h" #include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h" @@ -290,6 +291,7 @@ void Terrain::SetCollisionLOD(int32 value) void Terrain::SetPhysicalMaterials(const Array, FixedAllocation<8>>& value) { + PROFILE_MEM(LevelTerrain); _physicalMaterials = value; _physicalMaterials.Resize(8); JsonAsset* materials[8]; @@ -431,6 +433,7 @@ void Terrain::Setup(int32 lodCount, int32 chunkSize) void Terrain::AddPatches(const Int2& numberOfPatches) { + PROFILE_MEM(LevelTerrain); if (_chunkSize == 0) Setup(); _patches.ClearDelete(); @@ -470,6 +473,7 @@ void Terrain::AddPatch(const Int2& patchCoord) LOG(Warning, "Cannot add patch at {0}x{1}. The patch at the given location already exists.", patchCoord.X, patchCoord.Y); return; } + PROFILE_MEM(LevelTerrain); if (_chunkSize == 0) Setup(); @@ -726,6 +730,8 @@ void Terrain::Serialize(SerializeStream& stream, const void* otherObj) void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { + PROFILE_MEM(LevelTerrain); + // Base Actor::Deserialize(stream, modifier); diff --git a/Source/Engine/Terrain/TerrainManager.cpp b/Source/Engine/Terrain/TerrainManager.cpp index 594cdd39b..6da020079 100644 --- a/Source/Engine/Terrain/TerrainManager.cpp +++ b/Source/Engine/Terrain/TerrainManager.cpp @@ -5,16 +5,17 @@ #include "Engine/Threading/Threading.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Collections/ChunkedArray.h" #include "Engine/Core/Collections/Dictionary.h" -#include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Content/Content.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/AssetReference.h" -#include "Engine/Core/Log.h" -#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Renderer/DrawCall.h" +#include "Engine/Profiler/ProfilerMemory.h" // Must match structure defined in Terrain.shader struct TerrainVertex @@ -94,6 +95,7 @@ bool TerrainManager::GetChunkGeometry(DrawCall& drawCall, int32 chunkSize, int32 data->GetChunkGeometry(drawCall); return false; } + PROFILE_MEM(LevelTerrain); // Prepare const int32 vertexCount = (chunkSize + 1) >> lodIndex; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 1c754d843..65cdace25 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Physics/Physics.h" #include "Engine/Physics/PhysicsScene.h" #include "Engine/Physics/PhysicsBackend.h" @@ -66,6 +67,7 @@ TerrainPatch::TerrainPatch(const SpawnParams& params) void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) { + PROFILE_MEM(LevelTerrain); ScopeLock lock(_collisionLocker); _terrain = terrain; @@ -823,6 +825,7 @@ bool ModifyCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initDat bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask, bool forceUseVirtualStorage) { PROFILE_CPU_NAMED("Terrain.Setup"); + PROFILE_MEM(LevelTerrain); if (heightMap == nullptr) { LOG(Warning, "Cannot create terrain without a heightmap specified."); @@ -1034,6 +1037,7 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage) { PROFILE_CPU_NAMED("Terrain.SetupSplatMap"); + PROFILE_MEM(LevelTerrain); CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, true); if (splatMap == nullptr) { @@ -1182,6 +1186,7 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 bool TerrainPatch::InitializeHeightMap() { PROFILE_CPU_NAMED("Terrain.InitializeHeightMap"); + PROFILE_MEM(LevelTerrain); const auto heightmapSize = _terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1; Array heightmap; heightmap.Resize(heightmapSize * heightmapSize); @@ -1248,6 +1253,7 @@ void TerrainPatch::ClearCache() void TerrainPatch::CacheHeightData() { PROFILE_CPU_NAMED("Terrain.CacheHeightData"); + PROFILE_MEM(LevelTerrain); const TerrainDataUpdateInfo info(this); // Ensure that heightmap data is all loaded @@ -1313,6 +1319,7 @@ void TerrainPatch::CacheHeightData() void TerrainPatch::CacheSplatData() { PROFILE_CPU_NAMED("Terrain.CacheSplatData"); + PROFILE_MEM(LevelTerrain); const TerrainDataUpdateInfo info(this); // Cache all the splatmaps @@ -1396,6 +1403,7 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff return true; } PROFILE_CPU_NAMED("Terrain.ModifyHeightMap"); + PROFILE_MEM(LevelTerrain); // Check if has no heightmap if (Heightmap == nullptr) @@ -1490,6 +1498,7 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs return true; } PROFILE_CPU_NAMED("Terrain.ModifyHolesMask"); + PROFILE_MEM(LevelTerrain); // Check if has no heightmap if (Heightmap == nullptr) @@ -1567,6 +1576,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int return true; } PROFILE_CPU_NAMED("Terrain.ModifySplatMap"); + PROFILE_MEM(LevelTerrain); // Get the current data to modify it Color32* splatMap = GetSplatMapData(index); @@ -1738,6 +1748,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged) { PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); float* heightMap = GetHeightmapData(); byte* holesMask = GetHolesMaskData(); ASSERT(heightMap && holesMask); @@ -2126,6 +2137,7 @@ void TerrainPatch::UpdatePostManualDeserialization() void TerrainPatch::CreateCollision() { PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); ASSERT(!HasCollision()); if (CreateHeightField()) return; @@ -2241,6 +2253,7 @@ void TerrainPatch::DestroyCollision() void TerrainPatch::CacheDebugLines() { PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); ASSERT(_physicsHeightField); _debugLinesDirty = false; if (!_debugLines) @@ -2322,6 +2335,7 @@ void TerrainPatch::DrawPhysicsDebug(RenderView& view) const BoundingBox bounds(_bounds.Minimum - view.Origin, _bounds.Maximum - view.Origin); if (!_physicsShape || !view.CullingFrustum.Intersects(bounds)) return; + PROFILE_MEM(LevelTerrain); if (view.Mode == ViewMode::PhysicsColliders) { const auto& triangles = GetCollisionTriangles(); @@ -2378,6 +2392,7 @@ const Array& TerrainPatch::GetCollisionTriangles() if (!_physicsShape || _collisionTriangles.HasItems()) return _collisionTriangles; PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); int32 rows, cols; PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols); @@ -2428,6 +2443,7 @@ const Array& TerrainPatch::GetCollisionTriangles() void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& result) { PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); result.Clear(); // Skip if no intersection with patch @@ -2525,6 +2541,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& vertexBuffer, Array& indexBuffer) { PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); vertexBuffer.Clear(); indexBuffer.Clear(); From d95cd2f0be9ddf341bbaeb641662f4a06557686c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Jun 2025 22:41:29 +0200 Subject: [PATCH 27/84] Optimize memory alloc on Animated Model init --- .../Graphics/Models/SkinnedMeshDrawData.cpp | 23 ------------------- .../Graphics/Models/SkinnedMeshDrawData.h | 7 ------ Source/Engine/Level/Actors/AnimatedModel.cpp | 22 ++++++++++-------- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp index 2efe92181..8470facac 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp @@ -38,29 +38,6 @@ void SkinnedMeshDrawData::Setup(int32 bonesCount) SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices); } -void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) -{ - if (!bones) - return; - ANIM_GRAPH_PROFILE_EVENT("SetSkinnedMeshData"); - - // Copy bones to the buffer - const int32 count = BonesCount; - const int32 preFetchStride = 2; - const Matrix* input = bones; - const auto output = (Matrix3x4*)Data.Get(); - ASSERT(Data.Count() == count * sizeof(Matrix3x4)); - for (int32 i = 0; i < count; i++) - { - Matrix3x4* bone = output + i; - Platform::Prefetch(bone + preFetchStride); - Platform::Prefetch((byte*)(bone + preFetchStride) + PLATFORM_CACHE_LINE_SIZE); - bone->SetMatrixTranspose(input[i]); - } - - OnDataChanged(dropHistory); -} - void SkinnedMeshDrawData::OnDataChanged(bool dropHistory) { // Setup previous frame bone matrices if needed diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h index e690100be..24d5ca230 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h @@ -69,13 +69,6 @@ public: /// The bones count. void Setup(int32 bonesCount); - /// - /// Sets the bone matrices data for the GPU buffer. Ensure to call Flush before rendering. - /// - /// The bones data. - /// True if drop previous update bones used for motion blur, otherwise will keep them and do the update. - void SetData(const Matrix* bones, bool dropHistory); - /// /// After bones Data has been modified externally. Updates the bone matrices data for the GPU buffer. Ensure to call Flush before rendering. /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 7863578c6..1aad5f285 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -86,7 +86,8 @@ void AnimatedModel::PreInitSkinningData() { if (!SkinnedModel || !SkinnedModel->IsLoaded()) return; - + PROFILE_CPU(); + PROFILE_MEM(Animations); ScopeLock lock(SkinnedModel->Locker); SetupSkinningData(); @@ -96,28 +97,30 @@ void AnimatedModel::PreInitSkinningData() // Get nodes global transformations for the initial pose GraphInstance.NodesPose.Resize(nodesCount, false); + auto nodesPose = GraphInstance.NodesPose.Get(); for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) { Matrix localTransform; skeleton.Nodes[nodeIndex].LocalTransform.GetWorld(localTransform); const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex; if (parentIndex != -1) - GraphInstance.NodesPose[nodeIndex] = localTransform * GraphInstance.NodesPose[parentIndex]; + nodesPose[nodeIndex] = localTransform * nodesPose[parentIndex]; else - GraphInstance.NodesPose[nodeIndex] = localTransform; + nodesPose[nodeIndex] = localTransform; } GraphInstance.Invalidate(); - GraphInstance.RootTransform = skeleton.Nodes[0].LocalTransform; + GraphInstance.RootTransform = nodesCount > 0 ? skeleton.Nodes[0].LocalTransform : Transform::Identity; // Setup bones transformations including bone offset matrix - Array identityMatrices; // TODO: use shared memory? - identityMatrices.Resize(bonesCount, false); + Matrix3x4* output = (Matrix3x4*)_skinningData.Data.Get(); + const SkeletonBone* bones = skeleton.Bones.Get(); for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) { - auto& bone = skeleton.Bones[boneIndex]; - identityMatrices.Get()[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex]; + auto& bone = bones[boneIndex]; + Matrix identityMatrix = bone.OffsetMatrix * nodesPose[bone.NodeIndex]; + output[boneIndex].SetMatrixTranspose(identityMatrix); } - _skinningData.SetData(identityMatrices.Get(), true); + _skinningData.OnDataChanged(true); UpdateBounds(); UpdateSockets(); @@ -586,6 +589,7 @@ void AnimatedModel::SyncParameters() } else { + PROFILE_MEM(Animations); ScopeLock lock(AnimationGraph->Locker); // Clone the parameters From 462f75abd0c815dc0fba1813382e5397d22e7675 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Jun 2025 22:41:48 +0200 Subject: [PATCH 28/84] Optimize memory allocation when reading animated model pose by cloth --- Source/Engine/Level/Actors/AnimatedModel.cpp | 7 +++++++ Source/Engine/Level/Actors/AnimatedModel.h | 6 ++++++ Source/Engine/Physics/Actors/Cloth.cpp | 6 ++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 1aad5f285..ee95d6233 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -140,6 +140,13 @@ void AnimatedModel::GetCurrentPose(Array& nodesTransformation, bool worl } } +void AnimatedModel::GetCurrentPose(Span& nodesTransformation) const +{ + if (GraphInstance.NodesPose.IsEmpty()) + const_cast(this)->PreInitSkinningData(); // Ensure to have valid nodes pose to return + nodesTransformation = ToSpan(GraphInstance.NodesPose); +} + void AnimatedModel::SetCurrentPose(const Array& nodesTransformation, bool worldSpace) { if (GraphInstance.NodesPose.IsEmpty()) diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 89124cb87..e13e515d9 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -213,6 +213,12 @@ public: /// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor. API_FUNCTION() void GetCurrentPose(API_PARAM(Out) Array& nodesTransformation, bool worldSpace = false) const; + /// + /// Gets the per-node final transformations (skeleton pose). + /// + /// The output per-node final transformation matrices. + void GetCurrentPose(Span& nodesTransformation) const; + /// /// Sets the per-node final transformations (skeleton pose). /// diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index da7823517..a55cddd2a 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -815,8 +815,7 @@ bool Cloth::OnPreUpdate() Array particlesSkinned; particlesSkinned.Set(particles.Get(), particles.Length()); - // TODO: optimize memory allocs (eg. get pose as Span for readonly) - Array pose; + Span pose; animatedModel->GetCurrentPose(pose); const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton; const SkeletonBone* bones = skeleton.Bones.Get(); @@ -999,8 +998,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat return; } - // TODO: optimize memory allocs (eg. get pose as Span for readonly) - Array pose; + Span pose; animatedModel->GetCurrentPose(pose); const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton; From 125a973ff2482ee5b7c68c1124d9391405115b1b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Jun 2025 22:55:14 +0200 Subject: [PATCH 29/84] Rename `Prefetch` to `MemoryPrefetch` --- Source/Engine/Platform/Android/AndroidPlatform.h | 8 ++++---- Source/Engine/Platform/Apple/ApplePlatform.h | 7 ++++--- Source/Engine/Platform/Base/PlatformBase.h | 12 ++++++------ Source/Engine/Platform/Linux/LinuxPlatform.h | 8 ++++---- Source/Engine/Platform/Win32/Win32Platform.cpp | 2 +- Source/Engine/Platform/Win32/Win32Platform.h | 2 +- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Platform/Android/AndroidPlatform.h b/Source/Engine/Platform/Android/AndroidPlatform.h index 2d40c709f..4bae040cc 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.h +++ b/Source/Engine/Platform/Android/AndroidPlatform.h @@ -30,6 +30,10 @@ public: { __sync_synchronize(); } + FORCE_INLINE static void MemoryPrefetch(void const* ptr) + { + __builtin_prefetch(static_cast(ptr)); + } FORCE_INLINE static int64 InterlockedExchange(int64 volatile* dst, int64 exchange) { return __sync_lock_test_and_set(dst, exchange); @@ -74,10 +78,6 @@ public: { __atomic_store(dst, &value, __ATOMIC_RELAXED); } - FORCE_INLINE static void Prefetch(void const* ptr) - { - __builtin_prefetch(static_cast(ptr)); - } static bool Is64BitPlatform(); static String GetSystemName(); static Version GetSystemVersion(); diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h index cf91b8e22..02f6ac347 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.h +++ b/Source/Engine/Platform/Apple/ApplePlatform.h @@ -21,6 +21,10 @@ public: { __sync_synchronize(); } + FORCE_INLINE static void MemoryPrefetch(void const* ptr) + { + __builtin_prefetch(static_cast(ptr)); + } FORCE_INLINE static int64 InterlockedExchange(int64 volatile* dst, int64 exchange) { return __sync_lock_test_and_set(dst, exchange); @@ -62,9 +66,6 @@ public: __atomic_store_n((volatile int64*)dst, value, __ATOMIC_RELAXED); } FORCE_INLINE static void Prefetch(void const* ptr) - { - __builtin_prefetch(static_cast(ptr)); - } static bool Is64BitPlatform(); static String GetSystemName(); static Version GetSystemVersion(); diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index ff47e77a3..eeaeb879d 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -239,6 +239,12 @@ public: /// static void MemoryBarrier() = delete; + /// + /// Indicates to the processor that a cache line will be needed in the near future. + /// + /// The address of the cache line to be loaded. This address is not required to be on a cache line boundary. + static void Prefetch(void const* ptr) = delete; + /// /// Sets a 64-bit variable to the specified value as an atomic operation. The function prevents more than one thread from using the same variable simultaneously. /// @@ -317,12 +323,6 @@ public: /// The value to be set. static void AtomicStore(int64 volatile* dst, int64 value) = delete; - /// - /// Indicates to the processor that a cache line will be needed in the near future. - /// - /// The address of the cache line to be loaded. This address is not required to be on a cache line boundary. - static void Prefetch(void const* ptr) = delete; - #if COMPILE_WITH_PROFILER static void OnMemoryAlloc(void* ptr, uint64 size); static void OnMemoryFree(void* ptr); diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 08637dc87..98d9d2976 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -45,6 +45,10 @@ public: { __sync_synchronize(); } + FORCE_INLINE static void MemoryPrefetch(void const* ptr) + { + __builtin_prefetch(static_cast(ptr)); + } FORCE_INLINE static int64 InterlockedExchange(int64 volatile* dst, int64 exchange) { return __sync_lock_test_and_set(dst, exchange); @@ -89,10 +93,6 @@ public: { __atomic_store(dst, &value, __ATOMIC_SEQ_CST); } - FORCE_INLINE static void Prefetch(void const* ptr) - { - __builtin_prefetch(static_cast(ptr)); - } static bool Is64BitPlatform(); static String GetSystemName(); static Version GetSystemVersion(); diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index c002986c9..d32cb1249 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -251,7 +251,7 @@ void Win32Platform::MemoryBarrier() #endif } -void Win32Platform::Prefetch(void const* ptr) +void Win32Platform::MemoryPrefetch(void const* ptr) { #if _M_ARM64 __prefetch((char const*)ptr); diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h index 5763641ee..36d982bf7 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.h +++ b/Source/Engine/Platform/Win32/Win32Platform.h @@ -23,6 +23,7 @@ public: static bool Init(); static void Exit(); static void MemoryBarrier(); + static void MemoryPrefetch(void const* ptr); static int64 InterlockedExchange(int64 volatile* dst, int64 exchange) { #if WIN64 @@ -83,7 +84,6 @@ public: _interlockedexchange64(dst, value); #endif } - static void Prefetch(void const* ptr); static void* Allocate(uint64 size, uint64 alignment); static void Free(void* ptr); static void* AllocatePages(uint64 numPages, uint64 pageSize); From bffb175a9bac1e68b74478f855fc0f7c884b21d1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 7 Jun 2025 01:25:22 +0200 Subject: [PATCH 30/84] Code fixes --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 1 + Source/Engine/Animations/Graph/AnimGraph.cpp | 1 + Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp | 4 ++++ Source/Engine/Content/Assets/ModelBase.cpp | 1 + Source/Engine/Content/Assets/SkeletonMask.cpp | 1 + Source/Engine/Graphics/Models/MeshBase.cpp | 1 + Source/Engine/Graphics/RenderTargetPool.cpp | 1 + Source/Engine/Level/SceneObjectsFactory.h | 3 +++ Source/Engine/Navigation/NavMesh.cpp | 1 + Source/Engine/Navigation/NavMeshBuilder.cpp | 1 + Source/Engine/Networking/NetworkManager.cpp | 1 + Source/Engine/Terrain/Terrain.cpp | 1 + Source/Engine/Tools/ModelTool/ModelTool.cpp | 1 + 13 files changed, 18 insertions(+) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 81bf5ef35..030c31c41 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -36,6 +36,7 @@ #include "Engine/Engine/Base/GameBase.h" #include "Engine/Engine/Globals.h" #include "Engine/Tools/TextureTool/TextureTool.h" +#include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Enums.h" #if PLATFORM_TOOLS_WINDOWS diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 805d4747b..e99f53b8f 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -6,6 +6,7 @@ #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Scripting/Scripting.h" +#include "Engine/Threading/Threading.h" extern void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes); diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp index 2dc9e85ba..9600a93fb 100644 --- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp +++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Log.h" #include "Engine/Audio/Audio.h" #include "Engine/Threading/Threading.h" +#include "Engine/Profiler/ProfilerMemory.h" #if PLATFORM_WINDOWS // Tweak Win ver @@ -232,6 +233,7 @@ void AudioBackendXAudio2::Listener_ReinitializeAll() uint32 AudioBackendXAudio2::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) { + PROFILE_MEM(Audio); ScopeLock lock(XAudio2::Locker); // Get first free source @@ -580,6 +582,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(uint32 sourceID) uint32 AudioBackendXAudio2::Buffer_Create() { + PROFILE_MEM(Audio); uint32 bufferID; ScopeLock lock(XAudio2::Locker); @@ -618,6 +621,7 @@ void AudioBackendXAudio2::Buffer_Delete(uint32 bufferID) void AudioBackendXAudio2::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) { + PROFILE_MEM(Audio); CHECK(info.NumChannels <= MAX_INPUT_CHANNELS); XAudio2::Locker.Lock(); diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 7d16639ab..2521966a2 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -10,6 +10,7 @@ #include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" +#include "Engine/Threading/Threading.h" #if GPU_ENABLE_ASYNC_RESOURCES_CREATION #include "Engine/Threading/ThreadPoolTask.h" #define STREAM_TASK_BASE ThreadPoolTask diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp index 51d6e15c6..2776ba02c 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.cpp +++ b/Source/Engine/Content/Assets/SkeletonMask.cpp @@ -6,6 +6,7 @@ #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/SkeletonMaskUpgrader.h" +#include "Engine/Threading/Threading.h" REGISTER_BINARY_ASSET_WITH_UPGRADER(SkeletonMask, "FlaxEngine.SkeletonMask", SkeletonMaskUpgrader, true); diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index 62e65ee61..c62820748 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -16,6 +16,7 @@ #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Threading/Task.h" +#include "Engine/Threading/Threading.h" static_assert(MODEL_MAX_VB == 3, "Update code in mesh to match amount of vertex buffers."); diff --git a/Source/Engine/Graphics/RenderTargetPool.cpp b/Source/Engine/Graphics/RenderTargetPool.cpp index 9afd446e7..a1d243782 100644 --- a/Source/Engine/Graphics/RenderTargetPool.cpp +++ b/Source/Engine/Graphics/RenderTargetPool.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Log.h" #include "Engine/Engine/Engine.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Threading/Threading.h" struct Entry { diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index ab4138ea1..904bef696 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -4,6 +4,9 @@ #include "SceneObject.h" #include "Engine/Core/Collections/Dictionary.h" +#if USE_EDITOR +#include "Engine/Core/Collections/HashSet.h" +#endif #include "Engine/Platform/CriticalSection.h" #include "Engine/Threading/ThreadLocal.h" diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index 62017ad9e..6479542a1 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -4,6 +4,7 @@ #include "NavMeshRuntime.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Threading/Threading.h" #if COMPILE_WITH_ASSETS_IMPORTER #include "Engine/Core/Log.h" #include "Engine/ContentImporters/AssetsImportingManager.h" diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index d9f26522d..fdd938d9e 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -18,6 +18,7 @@ #include "Engine/Physics/Colliders/MeshCollider.h" #include "Engine/Physics/Colliders/SplineCollider.h" #include "Engine/Threading/ThreadPoolTask.h" +#include "Engine/Threading/Threading.h" #include "Engine/Terrain/TerrainPatch.h" #include "Engine/Terrain/Terrain.h" #include "Engine/Profiler/ProfilerCPU.h" diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index 3dc244bba..784bbf51e 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -16,6 +16,7 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Scripting.h" +#include "Engine/Threading/Threading.h" float NetworkManager::NetworkFPS = 60.0f; NetworkPeer* NetworkManager::Peer = nullptr; diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 85274b8fb..72ed1e7e1 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -4,6 +4,7 @@ #include "TerrainPatch.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Ray.h" +#include "Engine/Core/Collections/HashSet.h" #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Physics.h" diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index fe268c350..cdcd799b3 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -11,6 +11,7 @@ #include "Engine/Platform/ConditionVariable.h" #include "Engine/Profiler/Profiler.h" #include "Engine/Threading/JobSystem.h" +#include "Engine/Threading/Threading.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/RenderTools.h" From 73c30d3d8923cbfa21f8b1239f4f615cd7a14658 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jun 2025 00:58:15 +0200 Subject: [PATCH 31/84] Optimize asset references to support direct registration to reduce `Delegate` memory allocations and overhead --- Source/Engine/AI/Behavior.cpp | 15 +- Source/Engine/AI/Behavior.h | 7 +- Source/Engine/Audio/AudioSource.cpp | 11 +- Source/Engine/Audio/AudioSource.h | 8 +- Source/Engine/Content/Asset.cpp | 192 +++++++++++------- Source/Engine/Content/Asset.h | 34 +++- Source/Engine/Content/AssetReference.h | 33 ++- Source/Engine/Content/JsonAssetReference.h | 9 + Source/Engine/Content/SoftAssetReference.h | 16 +- Source/Engine/Content/WeakAssetReference.h | 18 +- Source/Engine/Level/Actors/AnimatedModel.cpp | 27 ++- Source/Engine/Level/Actors/AnimatedModel.h | 7 +- Source/Engine/Level/Actors/Sky.cpp | 1 - Source/Engine/Level/Actors/StaticModel.cpp | 11 +- Source/Engine/Level/Actors/StaticModel.h | 9 +- Source/Engine/Navigation/NavMesh.cpp | 12 +- Source/Engine/Navigation/NavMesh.h | 8 +- Source/Engine/Particles/ParticleEffect.cpp | 12 +- Source/Engine/Particles/ParticleEffect.h | 7 +- Source/Engine/Physics/Colliders/Collider.cpp | 28 ++- Source/Engine/Physics/Colliders/Collider.h | 10 +- .../Engine/Physics/Colliders/MeshCollider.cpp | 24 ++- .../Engine/Physics/Colliders/MeshCollider.h | 2 + Source/Engine/Renderer/ColorGradingPass.cpp | 1 - Source/Engine/Renderer/ForwardPass.cpp | 3 +- Source/Engine/Renderer/VolumetricFogPass.cpp | 1 - 26 files changed, 365 insertions(+), 141 deletions(-) diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index 7e647e945..442051d73 100644 --- a/Source/Engine/AI/Behavior.cpp +++ b/Source/Engine/AI/Behavior.cpp @@ -73,9 +73,9 @@ void BehaviorService::Dispose() Behavior::Behavior(const SpawnParams& params) : Script(params) + , Tree(this) { _knowledge.Behavior = this; - Tree.Changed.Bind(this); } void Behavior::UpdateAsync() @@ -175,6 +175,19 @@ void Behavior::OnDisable() BehaviorServiceInstance.UpdateList.Remove(this); } +void Behavior::OnAssetChanged(Asset* asset, void* caller) +{ + ResetLogic(); +} + +void Behavior::OnAssetLoaded(Asset* asset, void* caller) +{ +} + +void Behavior::OnAssetUnloaded(Asset* asset, void* caller) +{ +} + #if USE_EDITOR bool Behavior::GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior) diff --git a/Source/Engine/AI/Behavior.h b/Source/Engine/AI/Behavior.h index 4919847fa..b1c39ffc6 100644 --- a/Source/Engine/AI/Behavior.h +++ b/Source/Engine/AI/Behavior.h @@ -11,7 +11,7 @@ /// /// Behavior instance script that runs Behavior Tree execution. /// -API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script +API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script, private IAssetReference { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE(Behavior); @@ -92,6 +92,11 @@ public: void OnDisable() override; private: + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; + #if USE_EDITOR // Editor-only utilities to debug nodes state. API_FUNCTION(Internal) static bool GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior); diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index cff89e7e1..2a061ad48 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -21,9 +21,8 @@ AudioSource::AudioSource(const SpawnParams& params) , _playOnStart(false) , _startTime(0.0f) , _allowSpatialization(true) + , Clip(this) { - Clip.Changed.Bind(this); - Clip.Loaded.Bind(this); } void AudioSource::SetVolume(float value) @@ -264,7 +263,7 @@ void AudioSource::RequestStreamingBuffersUpdate() _needToUpdateStreamingBuffers = true; } -void AudioSource::OnClipChanged() +void AudioSource::OnAssetChanged(Asset* asset, void* caller) { Stop(); @@ -276,7 +275,7 @@ void AudioSource::OnClipChanged() } } -void AudioSource::OnClipLoaded() +void AudioSource::OnAssetLoaded(Asset* asset, void* caller) { if (!SourceID) return; @@ -302,6 +301,10 @@ void AudioSource::OnClipLoaded() } } +void AudioSource::OnAssetUnloaded(Asset* asset, void* caller) +{ +} + bool AudioSource::UseStreaming() const { if (Clip == nullptr || Clip->WaitForLoaded()) diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index b83a2b408..9d6d28ab4 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -13,7 +13,7 @@ /// Whether or not an audio source is spatial is controlled by the assigned AudioClip.The volume and the pitch of a spatial audio source is controlled by its position and the AudioListener's position/direction/velocity. /// API_CLASS(Attributes="ActorContextMenu(\"New/Audio/Audio Source\"), ActorToolbox(\"Other\")") -class FLAXENGINE_API AudioSource : public Actor +class FLAXENGINE_API AudioSource : public Actor, IAssetReference { DECLARE_SCENE_OBJECT(AudioSource); friend class AudioStreamingHandler; @@ -293,8 +293,10 @@ public: void RequestStreamingBuffersUpdate(); private: - void OnClipChanged(); - void OnClipLoaded(); + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; /// /// Plays the audio source. Should have buffer(s) binded before. diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index fd4fae421..86801b078 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/LogContext.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Threading/MainThreadTask.h" #include "Engine/Threading/ThreadLocal.h" @@ -34,15 +35,18 @@ bool ContentDeprecated::Clear(bool newValue) #endif +AssetReferenceBase::AssetReferenceBase(IAssetReference* owner) + : _owner(owner) +{ +} + AssetReferenceBase::~AssetReferenceBase() { Asset* asset = _asset; if (asset) { _asset = nullptr; - asset->OnLoaded.Unbind(this); - asset->OnUnloaded.Unbind(this); - asset->RemoveReference(); + asset->RemoveReference(this); } } @@ -51,52 +55,60 @@ String AssetReferenceBase::ToString() const return _asset ? _asset->ToString() : TEXT(""); } +void AssetReferenceBase::OnAssetChanged(Asset* asset, void* caller) +{ + if (_owner) + _owner->OnAssetChanged(asset, this); +} + +void AssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller) +{ + if (_asset != asset) + return; + Loaded(); + if (_owner) + _owner->OnAssetLoaded(asset, this); +} + +void AssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller) +{ + if (_asset != asset) + return; + Unload(); + OnSet(nullptr); + if (_owner) + _owner->OnAssetUnloaded(asset, this); +} + void AssetReferenceBase::OnSet(Asset* asset) { auto e = _asset; if (e != asset) { if (e) - { - e->OnLoaded.Unbind(this); - e->OnUnloaded.Unbind(this); - e->RemoveReference(); - } + e->RemoveReference(this); _asset = e = asset; if (e) - { - e->AddReference(); - e->OnLoaded.Bind(this); - e->OnUnloaded.Bind(this); - } + e->AddReference(this); Changed(); + if (_owner) + _owner->OnAssetChanged(asset, this); if (e && e->IsLoaded()) + { Loaded(); + if (_owner) + _owner->OnAssetLoaded(asset, this); + } } } -void AssetReferenceBase::OnLoaded(Asset* asset) -{ - if (_asset != asset) - return; - Loaded(); -} - -void AssetReferenceBase::OnUnloaded(Asset* asset) -{ - if (_asset != asset) - return; - Unload(); - OnSet(nullptr); -} - WeakAssetReferenceBase::~WeakAssetReferenceBase() { Asset* asset = _asset; if (asset) { _asset = nullptr; - asset->OnUnloaded.Unbind(this); + asset->RemoveReference(this, true); } } @@ -105,36 +117,43 @@ String WeakAssetReferenceBase::ToString() const return _asset ? _asset->ToString() : TEXT(""); } +void WeakAssetReferenceBase::OnAssetChanged(Asset* asset, void* caller) +{ +} + +void WeakAssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller) +{ +} + +void WeakAssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller) +{ + if (_asset != asset) + return; + Unload(); + asset->RemoveReference(this, true); + _asset = nullptr; +} + void WeakAssetReferenceBase::OnSet(Asset* asset) { auto e = _asset; if (e != asset) { if (e) - e->OnUnloaded.Unbind(this); + e->RemoveReference(this, true); _asset = e = asset; if (e) - e->OnUnloaded.Bind(this); + e->AddReference(this, true); } } -void WeakAssetReferenceBase::OnUnloaded(Asset* asset) -{ - if (_asset != asset) - return; - Unload(); - asset->OnUnloaded.Unbind(this); - _asset = nullptr; -} - SoftAssetReferenceBase::~SoftAssetReferenceBase() { Asset* asset = _asset; if (asset) { _asset = nullptr; - asset->OnUnloaded.Unbind(this); - asset->RemoveReference(); + asset->RemoveReference(this); } #if !BUILD_RELEASE _id = Guid::Empty; @@ -146,22 +165,34 @@ String SoftAssetReferenceBase::ToString() const return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("")); } +void SoftAssetReferenceBase::OnAssetChanged(Asset* asset, void* caller) +{ +} + +void SoftAssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller) +{ +} + +void SoftAssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller) +{ + if (_asset != asset) + return; + _asset->RemoveReference(this); + _asset = nullptr; + _id = Guid::Empty; + Changed(); +} + void SoftAssetReferenceBase::OnSet(Asset* asset) { if (_asset == asset) return; if (_asset) - { - _asset->OnUnloaded.Unbind(this); - _asset->RemoveReference(); - } + _asset->RemoveReference(this); _asset = asset; _id = asset ? asset->GetID() : Guid::Empty; if (asset) - { - asset->AddReference(); - asset->OnUnloaded.Bind(this); - } + asset->AddReference(this); Changed(); } @@ -170,10 +201,7 @@ void SoftAssetReferenceBase::OnSet(const Guid& id) if (_id == id) return; if (_asset) - { - _asset->OnUnloaded.Unbind(this); - _asset->RemoveReference(); - } + _asset->RemoveReference(this); _asset = nullptr; _id = id; Changed(); @@ -184,21 +212,7 @@ void SoftAssetReferenceBase::OnResolve(const ScriptingTypeHandle& type) ASSERT(!_asset); _asset = ::LoadAsset(_id, type); if (_asset) - { - _asset->OnUnloaded.Bind(this); - _asset->AddReference(); - } -} - -void SoftAssetReferenceBase::OnUnloaded(Asset* asset) -{ - if (_asset != asset) - return; - _asset->RemoveReference(); - _asset->OnUnloaded.Unbind(this); - _asset = nullptr; - _id = Guid::Empty; - Changed(); + _asset->AddReference(this); } Asset::Asset(const SpawnParams& params, const AssetInfo* info) @@ -216,6 +230,41 @@ int32 Asset::GetReferencesCount() const return (int32)Platform::AtomicRead(const_cast(&_refCount)); } +void Asset::AddReference() +{ + Platform::InterlockedIncrement(&_refCount); +} + +void Asset::AddReference(IAssetReference* ref, bool week) +{ + if (!week) + Platform::InterlockedIncrement(&_refCount); + if (ref) + { + //PROFILE_MEM(EngineDelegate); // Include references tracking memory within Delegate memory + Locker.Lock(); + _references.Add(ref); + Locker.Unlock(); + } +} + +void Asset::RemoveReference() +{ + Platform::InterlockedDecrement(&_refCount); +} + +void Asset::RemoveReference(IAssetReference* ref, bool week) +{ + if (ref) + { + Locker.Lock(); + _references.Remove(ref); + Locker.Unlock(); + } + if (!week) + Platform::InterlockedDecrement(&_refCount); +} + String Asset::ToString() const { return String::Format(TEXT("{0}, {1}, {2}"), GetTypeName(), GetID(), GetPath()); @@ -354,6 +403,7 @@ uint64 Asset::GetMemoryUsage() const if (Platform::AtomicRead(&_loadingTask)) result += sizeof(ContentLoadTask); result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType); + result += _references.Capacity() * sizeof(HashSet::Bucket); Locker.Unlock(); return result; } @@ -628,6 +678,8 @@ void Asset::onLoaded_MainThread() ASSERT(IsInMainThread()); // Send event + for (const auto& e : _references) + e.Item->OnAssetLoaded(this, this); OnLoaded(this); } @@ -641,6 +693,8 @@ void Asset::onUnload_MainThread() CancelStreaming(); // Send event + for (const auto& e : _references) + e.Item->OnAssetUnloaded(this, this); OnUnloaded(this); } diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index c16ea337e..17d8c8b5f 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -18,6 +18,18 @@ public: \ explicit type(const SpawnParams& params, const AssetInfo* info) +// Utility interface for objects that reference asset and want to get notified about asset reference changes. +class FLAXENGINE_API IAssetReference +{ +public: + // Asset reference got changed. + virtual void OnAssetChanged(Asset* asset, void* caller) = 0; + // Asset got loaded. + virtual void OnAssetLoaded(Asset* asset, void* caller) = 0; + // Asset gets unloaded. + virtual void OnAssetUnloaded(Asset* asset, void* caller) = 0; +}; + /// /// Asset objects base class. /// @@ -48,6 +60,8 @@ protected: int8 _deleteFileOnUnload : 1; // Indicates that asset source file should be removed on asset unload int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved) + HashSet _references; + public: /// /// Initializes a new instance of the class. @@ -88,18 +102,22 @@ public: /// /// Adds reference to that asset. /// - FORCE_INLINE void AddReference() - { - Platform::InterlockedIncrement(&_refCount); - } + void AddReference(); + + /// + /// Adds reference to that asset. + /// + void AddReference(IAssetReference* ref, bool week = false); /// /// Removes reference from that asset. /// - FORCE_INLINE void RemoveReference() - { - Platform::InterlockedDecrement(&_refCount); - } + void RemoveReference(); + + /// + /// Removes reference from that asset. + /// + void RemoveReference(IAssetReference* ref, bool week = false); public: /// diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h index 09e637e57..cd380a39c 100644 --- a/Source/Engine/Content/AssetReference.h +++ b/Source/Engine/Content/AssetReference.h @@ -7,10 +7,11 @@ /// /// Asset reference utility. Keeps reference to the linked asset object and handles load/unload events. /// -class FLAXENGINE_API AssetReferenceBase +class FLAXENGINE_API AssetReferenceBase : public IAssetReference { protected: Asset* _asset = nullptr; + IAssetReference* _owner = nullptr; public: /// @@ -36,6 +37,12 @@ public: /// AssetReferenceBase() = default; + /// + /// Initializes a new instance of the class. + /// + /// The reference owner to keep notified about asset changes. + AssetReferenceBase(IAssetReference* owner); + /// /// Finalizes an instance of the class. /// @@ -63,10 +70,14 @@ public: /// String ToString() const; +public: + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; + protected: void OnSet(Asset* asset); - void OnLoaded(Asset* asset); - void OnUnloaded(Asset* asset); }; /// @@ -87,6 +98,13 @@ public: { } + /// + /// Initializes a new instance of the class. + /// + explicit AssetReference(decltype(__nullptr)) + { + } + /// /// Initializes a new instance of the class. /// @@ -96,6 +114,15 @@ public: OnSet((Asset*)asset); } + /// + /// Initializes a new instance of the class. + /// + /// The reference owner to keep notified about asset changes. + explicit AssetReference(IAssetReference* owner) + : AssetReferenceBase(owner) + { + } + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h index 7e0ef528a..4325ca79e 100644 --- a/Source/Engine/Content/JsonAssetReference.h +++ b/Source/Engine/Content/JsonAssetReference.h @@ -19,6 +19,15 @@ API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference OnSet(asset); } + explicit JsonAssetReference(decltype(__nullptr)) + { + } + + explicit JsonAssetReference(IAssetReference* owner) + : AssetReference(owner) + { + } + /// /// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type. /// diff --git a/Source/Engine/Content/SoftAssetReference.h b/Source/Engine/Content/SoftAssetReference.h index e02fd1a4a..ef9adcde1 100644 --- a/Source/Engine/Content/SoftAssetReference.h +++ b/Source/Engine/Content/SoftAssetReference.h @@ -7,7 +7,7 @@ /// /// The asset soft reference. Asset gets referenced (loaded) on actual use (ID reference is resolving it). /// -class FLAXENGINE_API SoftAssetReferenceBase +class FLAXENGINE_API SoftAssetReferenceBase : public IAssetReference { protected: Asset* _asset = nullptr; @@ -46,11 +46,16 @@ public: /// String ToString() const; +public: + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; + protected: void OnSet(Asset* asset); void OnSet(const Guid& id); void OnResolve(const ScriptingTypeHandle& type); - void OnUnloaded(Asset* asset); }; /// @@ -71,6 +76,13 @@ public: { } + /// + /// Initializes a new instance of the class. + /// + explicit SoftAssetReference(decltype(__nullptr)) + { + } + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/Content/WeakAssetReference.h b/Source/Engine/Content/WeakAssetReference.h index c6df857d2..d67be0643 100644 --- a/Source/Engine/Content/WeakAssetReference.h +++ b/Source/Engine/Content/WeakAssetReference.h @@ -7,7 +7,7 @@ /// /// Asset reference utility that doesn't add reference to that asset. Handles asset unload event. /// -API_CLASS(InBuild) class WeakAssetReferenceBase +API_CLASS(InBuild) class WeakAssetReferenceBase : public IAssetReference { public: typedef Delegate<> EventType; @@ -56,9 +56,14 @@ public: /// String ToString() const; +public: + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; + protected: void OnSet(Asset* asset); - void OnUnloaded(Asset* asset); }; /// @@ -72,7 +77,13 @@ public: /// Initializes a new instance of the class. /// WeakAssetReference() - : WeakAssetReferenceBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + explicit WeakAssetReference(decltype(__nullptr)) { } @@ -81,7 +92,6 @@ public: /// /// The asset to set. WeakAssetReference(T* asset) - : WeakAssetReferenceBase() { OnSet(asset); } diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index ee95d6233..c4225ef94 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -27,16 +27,13 @@ AnimatedModel::AnimatedModel(const SpawnParams& params) , _counter(0) , _lastMinDstSqr(MAX_Real) , _lastUpdateFrame(0) + , SkinnedModel(this) + , AnimationGraph(this) { _drawCategory = SceneRendering::SceneDrawAsync; GraphInstance.Object = this; _box = BoundingBox(Vector3::Zero); _sphere = BoundingSphere(Vector3::Zero, 0.0f); - - SkinnedModel.Changed.Bind(this); - SkinnedModel.Loaded.Bind(this); - AnimationGraph.Changed.Bind(this); - AnimationGraph.Loaded.Bind(this); } AnimatedModel::~AnimatedModel() @@ -889,6 +886,26 @@ void AnimatedModel::OnGraphLoaded() SyncParameters(); } +void AnimatedModel::OnAssetChanged(Asset* asset, void* caller) +{ + if (caller == &SkinnedModel) + OnSkinnedModelChanged(); + else if (caller == &AnimationGraph) + OnGraphChanged(); +} + +void AnimatedModel::OnAssetLoaded(Asset* asset, void* caller) +{ + if (caller == &SkinnedModel) + OnSkinnedModelLoaded(); + else if (caller == &AnimationGraph) + OnGraphLoaded(); +} + +void AnimatedModel::OnAssetUnloaded(Asset* asset, void* caller) +{ +} + bool AnimatedModel::HasContentLoaded() const { return (SkinnedModel == nullptr || SkinnedModel->IsLoaded()) && Entries.HasContentLoaded(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index e13e515d9..6f87ab9cd 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -13,7 +13,7 @@ /// Performs an animation and renders a skinned model. /// API_CLASS(Attributes="ActorContextMenu(\"New/Animation/Animated Model\"), ActorToolbox(\"Visuals\")") -class FLAXENGINE_API AnimatedModel : public ModelInstanceActor +class FLAXENGINE_API AnimatedModel : public ModelInstanceActor, IAssetReference { DECLARE_SCENE_OBJECT(AnimatedModel); friend class AnimationsSystem; @@ -422,6 +422,11 @@ private: void OnGraphChanged(); void OnGraphLoaded(); + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; + public: // [ModelInstanceActor] bool HasContentLoaded() const override; diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 4e635489f..0aa3bf070 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -30,7 +30,6 @@ GPU_CB_STRUCT(Data { Sky::Sky(const SpawnParams& params) : Actor(params) - , _shader(nullptr) , _psSky(nullptr) , _psFog(nullptr) { diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 38c1eed90..912009b3b 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -29,10 +29,9 @@ StaticModel::StaticModel(const SpawnParams& params) , _vertexColorsDirty(false) , _vertexColorsCount(0) , _sortOrder(0) + , Model(this) { _drawCategory = SceneRendering::SceneDrawAsync; - Model.Changed.Bind(this); - Model.Loaded.Bind(this); } StaticModel::~StaticModel() @@ -224,7 +223,7 @@ void StaticModel::RemoveVertexColors() _vertexColorsDirty = false; } -void StaticModel::OnModelChanged() +void StaticModel::OnAssetChanged(Asset* asset, void* caller) { if (_residencyChangedModel) { @@ -241,7 +240,7 @@ void StaticModel::OnModelChanged() GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } -void StaticModel::OnModelLoaded() +void StaticModel::OnAssetLoaded(Asset* asset, void* caller) { Entries.SetupIfInvalid(Model); UpdateBounds(); @@ -316,6 +315,10 @@ void StaticModel::FlushVertexColors() RenderContext::GPULocker.Unlock(); } +void StaticModel::OnAssetUnloaded(Asset* asset, void* caller) +{ +} + bool StaticModel::HasContentLoaded() const { return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded(); diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index 09553f577..e6ed701cc 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -11,7 +11,7 @@ /// Renders model on the screen. /// API_CLASS(Attributes="ActorContextMenu(\"New/Model\"), ActorToolbox(\"Visuals\")") -class FLAXENGINE_API StaticModel : public ModelInstanceActor +class FLAXENGINE_API StaticModel : public ModelInstanceActor, IAssetReference { DECLARE_SCENE_OBJECT(StaticModel); private: @@ -154,11 +154,14 @@ public: API_FUNCTION() void RemoveVertexColors(); private: - void OnModelChanged(); - void OnModelLoaded(); void OnModelResidencyChanged(); void FlushVertexColors(); + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; + public: // [ModelInstanceActor] bool HasContentLoaded() const override; diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index 6479542a1..ee3e48f3e 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -17,8 +17,8 @@ NavMesh::NavMesh(const SpawnParams& params) : Actor(params) , IsDataDirty(false) + , DataAsset(this) { - DataAsset.Loaded.Bind(this); } void NavMesh::SaveNavMesh() @@ -100,7 +100,11 @@ void NavMesh::RemoveTiles() navMesh->RemoveTiles(this); } -void NavMesh::OnDataAssetLoaded() +void NavMesh::OnAssetChanged(Asset* asset, void* caller) +{ +} + +void NavMesh::OnAssetLoaded(Asset* asset, void* caller) { // Skip if already has data (prevent reloading navmesh on saving) if (Data.Tiles.HasItems()) @@ -126,6 +130,10 @@ void NavMesh::OnDataAssetLoaded() } } +void NavMesh::OnAssetUnloaded(Asset* asset, void* caller) +{ +} + void NavMesh::Serialize(SerializeStream& stream, const void* otherObj) { // Base diff --git a/Source/Engine/Navigation/NavMesh.h b/Source/Engine/Navigation/NavMesh.h index 40423696d..fcfbd27e2 100644 --- a/Source/Engine/Navigation/NavMesh.h +++ b/Source/Engine/Navigation/NavMesh.h @@ -15,7 +15,7 @@ class NavMeshRuntime; /// The navigation mesh actor that holds a navigation data for a scene. /// API_CLASS(Attributes="ActorContextMenu(\"New/Navigation/Nav Mesh\")") -class FLAXENGINE_API NavMesh : public Actor +class FLAXENGINE_API NavMesh : public Actor, IAssetReference { DECLARE_SCENE_OBJECT(NavMesh); public: @@ -67,7 +67,11 @@ public: private: void AddTiles(); void RemoveTiles(); - void OnDataAssetLoaded(); + + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; private: bool _navMeshActive = false; diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 1359dbcf2..93ccef55c 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -16,12 +16,10 @@ ParticleEffect::ParticleEffect(const SpawnParams& params) : Actor(params) , _lastUpdateFrame(0) , _lastMinDstSqr(MAX_Real) + , ParticleSystem(this) { _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); - - ParticleSystem.Changed.Bind(this); - ParticleSystem.Loaded.Bind(this); } void ParticleEffectParameter::Init(ParticleEffect* effect, int32 emitterIndex, int32 paramIndex) @@ -542,18 +540,22 @@ void ParticleEffect::ApplyModifiedParameters() } } -void ParticleEffect::OnParticleSystemModified() +void ParticleEffect::OnAssetChanged(Asset* asset, void* caller) { Instance.ClearState(); _parameters.Resize(0); _parametersVersion = 0; } -void ParticleEffect::OnParticleSystemLoaded() +void ParticleEffect::OnAssetLoaded(Asset* asset, void* caller) { ApplyModifiedParameters(); } +void ParticleEffect::OnAssetUnloaded(Asset* asset, void* caller) +{ +} + bool ParticleEffect::HasContentLoaded() const { if (ParticleSystem == nullptr) diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index 9be31c4c7..8529732dd 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -118,7 +118,7 @@ public: /// The particle system instance that plays the particles simulation in the game. /// API_CLASS(Attributes="ActorContextMenu(\"New/Visuals/Particle Effect\"), ActorToolbox(\"Visuals\")") -class FLAXENGINE_API ParticleEffect : public Actor +class FLAXENGINE_API ParticleEffect : public Actor, IAssetReference { DECLARE_SCENE_OBJECT(ParticleEffect); public: @@ -388,6 +388,11 @@ private: void OnParticleSystemModified(); void OnParticleSystemLoaded(); + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; + public: // [Actor] bool HasContentLoaded() const override; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 0ff51e8e6..5ebef0a9f 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -20,10 +20,8 @@ Collider::Collider(const SpawnParams& params) , _staticActor(nullptr) , _cachedScale(1.0f) , _contactOffset(2.0f) + , Material(this) { - Material.Loaded.Bind(this); - Material.Unload.Bind(this); - Material.Changed.Bind(this); } void* Collider::GetPhysicsShape() const @@ -294,13 +292,6 @@ void Collider::DrawPhysicsDebug(RenderView& view) #endif -void Collider::OnMaterialChanged() -{ - // Update the shape material - if (_shape) - PhysicsBackend::SetShapeMaterial(_shape, Material); -} - void Collider::BeginPlay(SceneBeginData* data) { // Check if has no shape created (it means no rigidbody requested it but also collider may be spawned at runtime) @@ -466,3 +457,20 @@ void Collider::OnPhysicsSceneChanged(PhysicsScene* previous) PhysicsBackend::AddSceneActor(scene, _staticActor); } } + +void Collider::OnAssetChanged(Asset* asset, void* caller) +{ + // Update the shape material + if (_shape && caller == &Material) + PhysicsBackend::SetShapeMaterial(_shape, Material); +} + +void Collider::OnAssetLoaded(Asset* asset, void* caller) +{ + Collider::OnAssetChanged(asset, caller); +} + +void Collider::OnAssetUnloaded(Asset* asset, void* caller) +{ + Collider::OnAssetChanged(asset, caller); +} diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 835d89a22..17e1d7883 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -15,7 +15,7 @@ class RigidBody; /// /// /// -API_CLASS(Abstract) class FLAXENGINE_API Collider : public PhysicsColliderActor +API_CLASS(Abstract) class FLAXENGINE_API Collider : public PhysicsColliderActor, protected IAssetReference { API_AUTO_SERIALIZATION(); DECLARE_SCENE_OBJECT_ABSTRACT(Collider); @@ -154,9 +154,6 @@ protected: /// void RemoveStaticActor(); -private: - void OnMaterialChanged(); - public: // [PhysicsColliderActor] RigidBody* GetAttachedRigidBody() const override; @@ -181,4 +178,9 @@ protected: void OnLayerChanged() override; void OnStaticFlagsChanged() override; void OnPhysicsSceneChanged(PhysicsScene* previous) override; + + // [IAssetReference] + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; + void OnAssetUnloaded(Asset* asset, void* caller) override; }; diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 0902f1106..e4e6948e3 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -11,9 +11,8 @@ MeshCollider::MeshCollider(const SpawnParams& params) : Collider(params) + , CollisionData(this) { - CollisionData.Changed.Bind(this); - CollisionData.Loaded.Bind(this); } void MeshCollider::OnCollisionDataChanged() @@ -33,8 +32,9 @@ void MeshCollider::OnCollisionDataChanged() void MeshCollider::OnCollisionDataLoaded() { - UpdateGeometry(); - UpdateBounds(); + // Not needed as OnCollisionDataChanged waits for it to be loaded + //UpdateGeometry(); + //UpdateBounds(); } bool MeshCollider::CanAttach(RigidBody* rigidBody) const @@ -152,3 +152,19 @@ void MeshCollider::GetGeometry(CollisionShape& collision) else collision.SetSphere(minSize); } + +void MeshCollider::OnAssetChanged(Asset* asset, void* caller) +{ + Collider::OnAssetChanged(asset, caller); + + if (caller == &CollisionData) + OnCollisionDataChanged(); +} + +void MeshCollider::OnAssetLoaded(Asset* asset, void* caller) +{ + Collider::OnAssetLoaded(asset, caller); + + if (caller == &CollisionData) + OnCollisionDataLoaded(); +} diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h index e6b1b7a82..89f013552 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.h +++ b/Source/Engine/Physics/Colliders/MeshCollider.h @@ -42,4 +42,6 @@ protected: #endif void UpdateBounds() override; void GetGeometry(CollisionShape& collision) override; + void OnAssetChanged(Asset* asset, void* caller) override; + void OnAssetLoaded(Asset* asset, void* caller) override; }; diff --git a/Source/Engine/Renderer/ColorGradingPass.cpp b/Source/Engine/Renderer/ColorGradingPass.cpp index 3b531b30f..43b49f091 100644 --- a/Source/Engine/Renderer/ColorGradingPass.cpp +++ b/Source/Engine/Renderer/ColorGradingPass.cpp @@ -39,7 +39,6 @@ GPU_CB_STRUCT(Data { ColorGradingPass::ColorGradingPass() : _useVolumeTexture(false) , _lutFormat() - , _shader(nullptr) { } diff --git a/Source/Engine/Renderer/ForwardPass.cpp b/Source/Engine/Renderer/ForwardPass.cpp index caf624609..c0d57b498 100644 --- a/Source/Engine/Renderer/ForwardPass.cpp +++ b/Source/Engine/Renderer/ForwardPass.cpp @@ -14,8 +14,7 @@ #include "Engine/Graphics/Shaders/GPUShader.h" ForwardPass::ForwardPass() - : _shader(nullptr) - , _psApplyDistortion(nullptr) + : _psApplyDistortion(nullptr) { } diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 817f25eef..c56812c72 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -19,7 +19,6 @@ int32 VolumetricFogGridInjectionGroupSize = 4; int32 VolumetricFogIntegrationGroupSize = 8; VolumetricFogPass::VolumetricFogPass() - : _shader(nullptr) { } From 99841e2e8d332d466f4e8391716c83a0cf59c9db Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jun 2025 00:58:31 +0200 Subject: [PATCH 32/84] Fix crash when using invalid node index in skinned mesh --- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 543f3791b..8c1b98ebf 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -158,6 +158,7 @@ void SkeletonData::Swap(SkeletonData& other) Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const { + CHECK_RETURN(Nodes.IsValidIndex(nodeIndex), Transform::Identity); const int32 parentIndex = Nodes[nodeIndex].ParentIndex; if (parentIndex == -1) { @@ -169,6 +170,7 @@ Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const void SkeletonData::SetNodeTransform(int32 nodeIndex, const Transform& value) { + CHECK(Nodes.IsValidIndex(nodeIndex)); const int32 parentIndex = Nodes[nodeIndex].ParentIndex; if (parentIndex == -1) { From 65ab42158d683b778264f009124c0a98fd927f8e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jun 2025 00:58:39 +0200 Subject: [PATCH 33/84] Update engine version --- Flax.flaxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index c51eb8bd6..96e09cd3d 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -2,9 +2,9 @@ "Name": "Flax", "Version": { "Major": 1, - "Minor": 10, + "Minor": 11, "Revision": 0, - "Build": 6705 + "Build": 6800 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.", From 907c593671b491d98e22fc5408e6133d8847187a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jun 2025 19:47:09 +0200 Subject: [PATCH 34/84] Fix typos in doc comments --- Source/Engine/Profiler/ProfilerMemory.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h index e42b8720e..5dddb912b 100644 --- a/Source/Engine/Profiler/ProfilerMemory.h +++ b/Source/Engine/Profiler/ProfilerMemory.h @@ -63,11 +63,11 @@ public: GraphicsIndexBuffers, // Total meshes memory usage (vertex and index buffers allocated by models). GraphicsMeshes, - // Totoal shaders memory usage (shaders bytecode, PSOs data). + // Total shaders memory usage (shaders bytecode, PSOs data). GraphicsShaders, - // Totoal materials memory usage (constant buffers, parameters data). + // Total materials memory usage (constant buffers, parameters data). GraphicsMaterials, - // Totoal command buffers memory usage (draw lists, constants uploads, ring buffer allocators). + // Total command buffers memory usage (draw lists, constants uploads, ring buffer allocators). GraphicsCommands, // Total Artificial Intelligence systems memory usage (eg. Behavior Trees). @@ -245,7 +245,7 @@ public: API_FUNCTION() static GroupsArray GetGroups(int32 mode = 0); /// - /// Dumps the memory allocations stats (groupped). + /// Dumps the memory allocations stats (grouped). /// /// 'all' to dump all groups, 'file' to dump info to a file (in Logs folder) API_FUNCTION(Attributes="DebugCommand") static void Dump(const StringView& options = StringView::Empty); From 6547e7ee9c829c118e908b1d86910e0ff8f64b42 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 8 Jun 2025 23:58:33 +0200 Subject: [PATCH 35/84] Fix compilation with Clang --- Source/Engine/Content/Asset.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index 17d8c8b5f..bb0fbe490 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -22,6 +22,8 @@ class FLAXENGINE_API IAssetReference { public: + virtual ~IAssetReference() = default; + // Asset reference got changed. virtual void OnAssetChanged(Asset* asset, void* caller) = 0; // Asset got loaded. From 7fa4efcac5500c802e45daaf65c7f56c287daef0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Jun 2025 10:17:51 +0200 Subject: [PATCH 36/84] Fix compilation in Release --- Source/Engine/Core/Delegate.h | 2 ++ Source/Engine/Level/Actors/AnimatedModel.cpp | 1 + Source/Engine/Utilities/Screenshot.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index 8efc2beec..00614a9ff 100644 --- a/Source/Engine/Core/Delegate.h +++ b/Source/Engine/Core/Delegate.h @@ -460,7 +460,9 @@ public: /// The function to bind. void Bind(const FunctionType& f) { +#if COMPILE_WITH_PROFILER PROFILE_MEM(EngineDelegate); +#endif #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead(&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index c4225ef94..ec50cfd56 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -19,6 +19,7 @@ #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneObjectsFactory.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/Serialization.h" AnimatedModel::AnimatedModel(const SpawnParams& params) diff --git a/Source/Engine/Utilities/Screenshot.cpp b/Source/Engine/Utilities/Screenshot.cpp index 9a453701d..e8e6baf8d 100644 --- a/Source/Engine/Utilities/Screenshot.cpp +++ b/Source/Engine/Utilities/Screenshot.cpp @@ -13,6 +13,7 @@ #include "Engine/Graphics/GPUSwapChain.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Engine/Globals.h" +#include "Engine/Profiler/ProfilerMemory.h" #if COMPILE_WITH_TEXTURE_TOOL #include "Engine/Tools/TextureTool/TextureTool.h" #endif From 057ec9d41ed1d7e0f78b2469c85775db8182bfa6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Jun 2025 10:48:02 +0200 Subject: [PATCH 37/84] Anothher fix --- Source/Engine/Platform/Apple/ApplePlatform.h | 1 - Source/Engine/Platform/Base/PlatformBase.h | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h index 02f6ac347..003cec91a 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.h +++ b/Source/Engine/Platform/Apple/ApplePlatform.h @@ -65,7 +65,6 @@ public: { __atomic_store_n((volatile int64*)dst, value, __ATOMIC_RELAXED); } - FORCE_INLINE static void Prefetch(void const* ptr) static bool Is64BitPlatform(); static String GetSystemName(); static Version GetSystemVersion(); diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index eeaeb879d..40245a3fc 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -186,7 +186,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(PlatformBase); static void BeforeExit(); /// - /// Called after engine exit to shutdown platform service. + /// Called after engine exit to shut down platform service. /// static void Exit(); @@ -243,7 +243,7 @@ public: /// Indicates to the processor that a cache line will be needed in the near future. /// /// The address of the cache line to be loaded. This address is not required to be on a cache line boundary. - static void Prefetch(void const* ptr) = delete; + static void MemoryPrefetch(void const* ptr) = delete; /// /// Sets a 64-bit variable to the specified value as an atomic operation. The function prevents more than one thread from using the same variable simultaneously. @@ -256,7 +256,7 @@ public: /// /// Performs an atomic compare-and-exchange operation on the specified values. The function compares two specified 32-bit values and exchanges with another 32-bit value based on the outcome of the comparison. /// - /// The function compares the dst value with the comperand value. If the dst value is equal to the comperand value, the value value is stored in the address specified by dst. Otherwise, no operation is performed. + /// The function compares the dst value with the comperand value. If the dst value is equal to the comperand value, the value is stored in the address specified by dst. Otherwise, no operation is performed. /// A pointer to the first operand. This value will be replaced with the result of the operation. /// The value to exchange. /// The value to compare to destination. @@ -266,7 +266,7 @@ public: /// /// Performs an atomic compare-and-exchange operation on the specified values. The function compares two specified 64-bit values and exchanges with another 64-bit value based on the outcome of the comparison. /// - /// The function compares the dst value with the comperand value. If the dst value is equal to the comperand value, the value value is stored in the address specified by dst. Otherwise, no operation is performed. + /// The function compares the dst value with the comperand value. If the dst value is equal to the comperand value, the value is stored in the address specified by dst. Otherwise, no operation is performed. /// A pointer to the first operand. This value will be replaced with the result of the operation. /// The value to exchange. /// The value to compare to destination. From d7ff9fdadebe2566d23216c30387a88f873865df Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Jun 2025 15:23:31 +0200 Subject: [PATCH 38/84] Optimize editor profiler native allocations when capturing data --- Source/Engine/Content/Content.cpp | 17 +++++++++++++++++ Source/Engine/Content/Content.cs | 14 ++++++++++++++ Source/Engine/Content/Content.h | 7 ++++++- Source/Engine/Engine/NativeInterop.cs | 5 ++++- Source/Engine/Graphics/GPUBufferDescription.cs | 18 ++++++++++++++++++ Source/Engine/Graphics/GPUDevice.cpp | 17 +++++++++++++++++ Source/Engine/Graphics/GPUDevice.h | 8 +++++++- 7 files changed, 83 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 915f48140..4ba6cbc44 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -800,6 +800,23 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id) #endif } +#if !COMPILE_WITHOUT_CSHARP + +#include "Engine/Scripting/ManagedCLR/MUtils.h" + +void* Content::GetAssetsInternal() +{ + AssetsLocker.Lock(); + MArray* result = MCore::Array::New(Asset::TypeInitializer.GetClass(), Assets.Count()); + int32 i = 0; + for (const auto& e : Assets) + MCore::GC::WriteArrayRef(result, e.Value->GetOrCreateManagedInstance(), i++); + AssetsLocker.Unlock(); + return result; +} + +#endif + #if USE_EDITOR bool Content::RenameAsset(const StringView& oldPath, const StringView& newPath) diff --git a/Source/Engine/Content/Content.cs b/Source/Engine/Content/Content.cs index 4fcc0c300..4f07e1dc6 100644 --- a/Source/Engine/Content/Content.cs +++ b/Source/Engine/Content/Content.cs @@ -1,5 +1,6 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using FlaxEngine.Interop; using System; using System.Runtime.CompilerServices; @@ -7,6 +8,19 @@ namespace FlaxEngine { partial class Content { + /// + /// Gets the assets (loaded or during load). + /// + public static Asset[] Assets + { + get + { + IntPtr ptr = Internal_GetAssetsInternal(); + ManagedArray array = Unsafe.As(ManagedHandle.FromIntPtr(ptr).Target); + return NativeInterop.GCHandleArrayToManagedArray(array); + } + } + /// /// Loads asset to the Content Pool and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async. /// diff --git a/Source/Engine/Content/Content.h b/Source/Engine/Content/Content.h index c11a9ed11..15ace944a 100644 --- a/Source/Engine/Content/Content.h +++ b/Source/Engine/Content/Content.h @@ -122,7 +122,7 @@ public: /// Gets the assets (loaded or during load). /// /// The collection of assets. - API_PROPERTY() static Array GetAssets(); + static Array GetAssets(); /// /// Gets the raw dictionary of assets (loaded or during load). @@ -368,4 +368,9 @@ private: static void onAssetUnload(Asset* asset); static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId); static void deleteFileSafety(const StringView& path, const Guid& id); + + // Internal bindings +#if !COMPILE_WITHOUT_CSHARP + API_FUNCTION(NoProxy) static void* GetAssetsInternal(); +#endif }; diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 368c67132..8138d3604 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -201,7 +201,10 @@ namespace FlaxEngine.Interop Span span = ptrArray.ToSpan(); T[] managedArray = new T[ptrArray.Length]; for (int i = 0; i < managedArray.Length; i++) - managedArray[i] = span[i] != IntPtr.Zero ? (T)ManagedHandle.FromIntPtr(span[i]).Target : default; + { + IntPtr ptr = span[i]; + managedArray[i] = ptr != IntPtr.Zero ? (T)ManagedHandle.FromIntPtr(ptr).Target : default; + } return managedArray; } diff --git a/Source/Engine/Graphics/GPUBufferDescription.cs b/Source/Engine/Graphics/GPUBufferDescription.cs index 290e43a7a..9e52876fc 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.cs +++ b/Source/Engine/Graphics/GPUBufferDescription.cs @@ -1,9 +1,27 @@ // Copyright (c) Wojciech Figat. All rights reserved. using System; +using System.Runtime.CompilerServices; +using FlaxEngine.Interop; namespace FlaxEngine { + partial class GPUDevice + { + /// + /// Gets the list with all active GPU resources. + /// + public GPUResource[] Resources + { + get + { + IntPtr ptr = Internal_GetResourcesInternal(__unmanagedPtr); + ManagedArray array = Unsafe.As(ManagedHandle.FromIntPtr(ptr).Target); + return NativeInterop.GCHandleArrayToManagedArray(array); + } + } + } + partial struct GPUBufferDescription : IEquatable { /// diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 1ea008913..18b9cdffc 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -648,6 +648,23 @@ GPUTasksExecutor* GPUDevice::CreateTasksExecutor() return New(); } +#if !COMPILE_WITHOUT_CSHARP + +#include "Engine/Scripting/ManagedCLR/MUtils.h" + +void* GPUDevice::GetResourcesInternal() +{ + _resourcesLock.Lock(); + MArray* result = MCore::Array::New(GPUResource::TypeInitializer.GetClass(), _resources.Count()); + int32 i = 0; + for (const auto& e : _resources) + MCore::GC::WriteArrayRef(result, e->GetOrCreateManagedInstance(), i++); + _resourcesLock.Unlock(); + return result; +} + +#endif + void GPUDevice::Draw() { PROFILE_MEM(Graphics); diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index c54395df8..8914085eb 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -236,7 +236,7 @@ public: /// /// Gets the list with all active GPU resources. /// - API_PROPERTY() Array GetResources() const; + Array GetResources() const; /// /// Gets the GPU asynchronous work manager. @@ -432,6 +432,12 @@ public: /// /// The GPU tasks executor. virtual GPUTasksExecutor* CreateTasksExecutor(); + +private: + // Internal bindings +#if !COMPILE_WITHOUT_CSHARP + API_FUNCTION(NoProxy) void* GetResourcesInternal(); +#endif }; /// From 89c7f4b0a3cca616169301d9d6c816e49e9fd165 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Jun 2025 17:19:36 +0200 Subject: [PATCH 39/84] Fix `ManagedDictionary` cache to be cleared on hot-reload --- .../Scripting/Internal/ManagedDictionary.cpp | 2 +- .../Scripting/Internal/ManagedDictionary.h | 17 +++++++++-------- Source/Engine/Scripting/Scripting.cpp | 2 ++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp index d467fe47a..d2f74e054 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp @@ -3,7 +3,7 @@ #include "ManagedDictionary.h" #if USE_CSHARP -Dictionary ManagedDictionary::CachedDictionaryTypes; +Dictionary ManagedDictionary::CachedTypes; #if !USE_MONO_AOT ManagedDictionary::MakeGenericTypeThunk ManagedDictionary::MakeGenericType; ManagedDictionary::CreateInstanceThunk ManagedDictionary::CreateInstance; diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.h b/Source/Engine/Scripting/Internal/ManagedDictionary.h index af88172b6..5e2638af7 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.h +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.h @@ -22,17 +22,18 @@ struct FLAXENGINE_API ManagedDictionary public: struct KeyValueType { - MType* keyType; - MType* valueType; + MType* KeyType; + MType* ValueType; bool operator==(const KeyValueType& other) const { - return keyType == other.keyType && valueType == other.valueType; + return KeyType == other.KeyType && ValueType == other.ValueType; } }; private: - static Dictionary CachedDictionaryTypes; + friend class Scripting; + static Dictionary CachedTypes; #if !USE_MONO_AOT typedef MTypeObject* (*MakeGenericTypeThunk)(MObject* instance, MTypeObject* genericType, MArray* genericArgs, MObject** exception); @@ -158,7 +159,7 @@ public: // Check if the generic type was generated earlier KeyValueType cacheKey = { keyType, valueType }; MTypeObject* dictionaryType; - if (CachedDictionaryTypes.TryGet(cacheKey, dictionaryType)) + if (CachedTypes.TryGet(cacheKey, dictionaryType)) return dictionaryType; MTypeObject* genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass); @@ -186,7 +187,7 @@ public: ex.Log(LogType::Error, TEXT("")); return nullptr; } - CachedDictionaryTypes.Add(cacheKey, dictionaryType); + CachedTypes.Add(cacheKey, dictionaryType); return dictionaryType; } @@ -264,8 +265,8 @@ public: inline uint32 GetHash(const ManagedDictionary::KeyValueType& other) { - uint32 hash = ::GetHash((void*)other.keyType); - CombineHash(hash, ::GetHash((void*)other.valueType)); + uint32 hash = ::GetHash((void*)other.KeyType); + CombineHash(hash, ::GetHash((void*)other.ValueType)); return hash; } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 3a69a7601..a17de075b 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -21,6 +21,7 @@ #include "ManagedCLR/MCore.h" #include "ManagedCLR/MException.h" #include "Internal/StdTypesContainer.h" +#include "Internal/ManagedDictionary.h" #include "Engine/Core/LogContext.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Core/Types/TimeSpan.h" @@ -720,6 +721,7 @@ void Scripting::Reload(bool canTriggerSceneReload) modules.Clear(); _nonNativeModules.ClearDelete(); _hasGameModulesLoaded = false; + ManagedDictionary::CachedTypes.Clear(); // Release and create a new assembly load context for user assemblies MCore::UnloadScriptingAssemblyLoadContext(); From cfd2f42b0cf3787f6f502fde7f885a369a909936 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Jun 2025 22:06:49 +0200 Subject: [PATCH 40/84] Optimize managed memory allocations in Editor profiler --- Source/Editor/Windows/Profiler/Assets.cs | 11 +++++++---- Source/Editor/Windows/Profiler/MemoryGPU.cs | 11 +++++++---- Source/Engine/Content/Content.cs | 14 ++++++++++++++ Source/Engine/Engine/NativeInterop.cs | 12 +++++++----- Source/Engine/Graphics/GPUBufferDescription.cs | 14 ++++++++++++++ 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/Source/Editor/Windows/Profiler/Assets.cs b/Source/Editor/Windows/Profiler/Assets.cs index 0ad47735f..e4e41c668 100644 --- a/Source/Editor/Windows/Profiler/Assets.cs +++ b/Source/Editor/Windows/Profiler/Assets.cs @@ -34,6 +34,7 @@ namespace FlaxEditor.Windows.Profiler private List _tableRowsCache; private Dictionary _resourceCache; private StringBuilder _stringBuilder; + private Asset[] _assetsCache; public Assets() : base("Assets") @@ -138,12 +139,12 @@ namespace FlaxEditor.Windows.Profiler _stringBuilder = new StringBuilder(); // Capture current assets usage info - var assets = FlaxEngine.Content.Assets; - var resources = new Resource[assets.Length]; + FlaxEngine.Content.GetAssets(ref _assetsCache, out var count); + var resources = new Resource[count]; ulong totalMemoryUsage = 0; - for (int i = 0; i < resources.Length; i++) + for (int i = 0; i < count; i++) { - var asset = assets[i]; + var asset = _assetsCache[i]; ref var resource = ref resources[i]; if (!asset) continue; @@ -179,6 +180,7 @@ namespace FlaxEditor.Windows.Profiler if (_resources == null) _resources = new SamplesBuffer(); _resources.Add(resources); + Array.Clear(_assetsCache); } /// @@ -200,6 +202,7 @@ namespace FlaxEditor.Windows.Profiler _resourceCache?.Clear(); _tableRowsCache?.Clear(); _stringBuilder?.Clear(); + _assetsCache = null; base.OnDestroy(); } diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs index acaeca364..ce266777d 100644 --- a/Source/Editor/Windows/Profiler/MemoryGPU.cs +++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs @@ -35,6 +35,7 @@ namespace FlaxEditor.Windows.Profiler private Dictionary _assetPathToId; private Dictionary _resourceCache; private StringBuilder _stringBuilder; + private GPUResource[] _gpuResourcesCached; public MemoryGPU() : base("GPU Memory") @@ -138,12 +139,12 @@ namespace FlaxEditor.Windows.Profiler // Capture current GPU resources usage info var contentDatabase = Editor.Instance.ContentDatabase; - var gpuResources = GPUDevice.Instance.Resources; - var resources = new Resource[gpuResources.Length]; + GPUDevice.Instance.GetResources(ref _gpuResourcesCached, out var count); + var resources = new Resource[count]; var sb = _stringBuilder; - for (int i = 0; i < resources.Length; i++) + for (int i = 0; i < count; i++) { - var gpuResource = gpuResources[i]; + var gpuResource = _gpuResourcesCached[i]; ref var resource = ref resources[i]; // Try to reuse cached resource info @@ -219,6 +220,7 @@ namespace FlaxEditor.Windows.Profiler if (_resources == null) _resources = new SamplesBuffer(); _resources.Add(resources); + Array.Clear(_gpuResourcesCached); } /// @@ -255,6 +257,7 @@ namespace FlaxEditor.Windows.Profiler _assetPathToId?.Clear(); _tableRowsCache?.Clear(); _stringBuilder?.Clear(); + _gpuResourcesCached = null; base.OnDestroy(); } diff --git a/Source/Engine/Content/Content.cs b/Source/Engine/Content/Content.cs index 4f07e1dc6..010abbc56 100644 --- a/Source/Engine/Content/Content.cs +++ b/Source/Engine/Content/Content.cs @@ -21,6 +21,20 @@ namespace FlaxEngine } } + /// + /// Gets the assets (loaded or during load). + /// + /// Output buffer to fill with asset pointers. Can be provided by a user to avoid memory allocation. Buffer might be larger than actual list size. Use for actual item count.> + /// Amount of valid items inside . + public static void GetAssets(ref Asset[] buffer, out int count) + { + count = 0; + IntPtr ptr = Internal_GetAssetsInternal(); + ManagedArray array = Unsafe.As(ManagedHandle.FromIntPtr(ptr).Target); + buffer = NativeInterop.GCHandleArrayToManagedArray(array, buffer); + count = buffer.Length; + } + /// /// Loads asset to the Content Pool and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async. /// diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 8138d3604..7d16b4752 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -195,17 +195,19 @@ namespace FlaxEngine.Interop /// /// Array element type. /// Input array. + /// Cached memory allocation buffer to use for the result (if size fits). /// Output array. - public static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) where T : class + public static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray, T[] buffer = null) where T : class { Span span = ptrArray.ToSpan(); - T[] managedArray = new T[ptrArray.Length]; - for (int i = 0; i < managedArray.Length; i++) + if (buffer == null || buffer.Length < ptrArray.Length) + buffer = new T[ptrArray.Length]; + for (int i = 0; i < ptrArray.Length; i++) { IntPtr ptr = span[i]; - managedArray[i] = ptr != IntPtr.Zero ? (T)ManagedHandle.FromIntPtr(ptr).Target : default; + buffer[i] = ptr != IntPtr.Zero ? (T)ManagedHandle.FromIntPtr(ptr).Target : default; } - return managedArray; + return buffer; } /// diff --git a/Source/Engine/Graphics/GPUBufferDescription.cs b/Source/Engine/Graphics/GPUBufferDescription.cs index 9e52876fc..107d17b3e 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.cs +++ b/Source/Engine/Graphics/GPUBufferDescription.cs @@ -20,6 +20,20 @@ namespace FlaxEngine return NativeInterop.GCHandleArrayToManagedArray(array); } } + + /// + /// Gets the list with all active GPU resources. + /// + /// Output buffer to fill with resource pointers. Can be provided by a user to avoid memory allocation. Buffer might be larger than actual list size. Use for actual item count.> + /// Amount of valid items inside . + public void GetResources(ref GPUResource[] buffer, out int count) + { + count = 0; + IntPtr ptr = Internal_GetResourcesInternal(__unmanagedPtr); + ManagedArray array = Unsafe.As(ManagedHandle.FromIntPtr(ptr).Target); + buffer = NativeInterop.GCHandleArrayToManagedArray(array, buffer); + count = buffer.Length; + } } partial struct GPUBufferDescription : IEquatable From d6b4992991fddc61059fda3f3dca36ef2b7e2740 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Jun 2025 20:08:20 +0200 Subject: [PATCH 41/84] Optimize actors registration in `SceneRendering` to track free items --- Source/Engine/Level/Scene/SceneRendering.cpp | 17 +++++++++++------ Source/Engine/Level/Scene/SceneRendering.h | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index c6f5669a5..fe2bc310f 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -136,6 +136,8 @@ void SceneRendering::Clear() _listeners.Clear(); for (auto& e : Actors) e.Clear(); + for (auto& e : FreeActors) + e.Clear(); #if USE_EDITOR PhysicsDebug.Clear(); #endif @@ -149,15 +151,17 @@ void SceneRendering::AddActor(Actor* a, int32& key) const int32 category = a->_drawCategory; ScopeLock lock(Locker); auto& list = Actors[category]; - // TODO: track removedCount and skip searching for free entry if there is none - key = 0; - for (; key < list.Count(); key++) + if (FreeActors[category].HasItems()) { - if (list.Get()[key].Actor == nullptr) - break; + // Use existing item + key = FreeActors[category].Pop(); } - if (key == list.Count()) + else + { + // Add a new item + key = list.Count(); list.AddOne(); + } auto& e = list[key]; e.Actor = a; e.LayerMask = a->GetLayerMask(); @@ -200,6 +204,7 @@ void SceneRendering::RemoveActor(Actor* a, int32& key) listener->OnSceneRenderingRemoveActor(a); e.Actor = nullptr; e.LayerMask = 0; + FreeActors[category].Add(key); } } key = -1; diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index 043f5079e..b24dcdfa9 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -100,6 +100,7 @@ public: }; Array Actors[MAX]; + Array FreeActors[MAX]; Array PostFxProviders; CriticalSection Locker; From b50f3fcb64932c3c63f26f6807b56ca56a25c621 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Jun 2025 00:01:46 +0200 Subject: [PATCH 42/84] Refactor level actions to support time budget and time slicing --- Source/Engine/Core/Types/Stopwatch.h | 2 +- Source/Engine/Level/Level.cpp | 239 +++++++++++++++++---------- Source/Engine/Level/Level.h | 13 +- Source/Engine/Level/Scene/Scene.h | 1 + 4 files changed, 156 insertions(+), 99 deletions(-) diff --git a/Source/Engine/Core/Types/Stopwatch.h b/Source/Engine/Core/Types/Stopwatch.h index c909285af..d87df0f21 100644 --- a/Source/Engine/Core/Types/Stopwatch.h +++ b/Source/Engine/Core/Types/Stopwatch.h @@ -43,7 +43,7 @@ public: /// /// Gets the total number of milliseconds. /// - FORCE_INLINE double GetTotalMilliseconds() const + FORCE_INLINE float GetTotalMilliseconds() const { return (float)((_end - _start) * 1000.0); } diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 1233282be..d3f1ccba3 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -18,16 +18,15 @@ #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" #include "Engine/Debug/Exceptions/JsonParseException.h" +#include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Threading/Threading.h" #include "Engine/Threading/JobSystem.h" #include "Engine/Platform/File.h" -#include "Engine/Platform/FileSystem.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/Script.h" #include "Engine/Engine/Time.h" -#include "Engine/Scripting/ManagedCLR/MAssembly.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MDomain.h" #include "Engine/Scripting/ManagedCLR/MException.h" @@ -78,6 +77,13 @@ enum class SceneEventType OnSceneUnloaded = 7, }; +enum class SceneResult +{ + Success, + Failed, + Wait, +}; + class SceneAction { public: @@ -85,14 +91,15 @@ public: { } - virtual bool CanDo() const + struct Context { - return true; - } + // Amount of seconds that action can take to run within a budget. + float TimeBudget = MAX_float; + }; - virtual bool Do() const + virtual SceneResult Do(Context& context) { - return true; + return SceneResult::Failed; } }; @@ -107,6 +114,33 @@ struct ScriptsReloadObject #endif +// Async map loading utility for state tracking and synchronization of various load stages. +class SceneLoader +{ +public: + enum Stages + { + Init, + Spawn, + SetupPrefabs, + Deserialize, + SetupTransforms, + BeginPlay, + Loaded, + } Stage = Init; + bool AsyncLoad; + bool AsyncJobs; + float TotalTime = 0.0f; + + SceneLoader(bool asyncLoad = false) + : AsyncLoad(true) + , AsyncJobs(JobSystem::GetThreadsCount() > 1) + { + } + + SceneResult Tick(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget); +}; + namespace LevelImpl { Array _sceneActions; @@ -119,6 +153,10 @@ namespace LevelImpl void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId); void flushActions(); + SceneResult loadScene(SceneLoader& loader, JsonAsset* sceneAsset); + SceneResult loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene = nullptr); + SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene = nullptr); + SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, const String* assetPath = nullptr, float* timeBudget = nullptr); bool unloadScene(Scene* scene); bool unloadScenes(); bool saveScene(Scene* scene); @@ -151,6 +189,7 @@ LevelService LevelServiceInstanceService; CriticalSection Level::ScenesLock; Array Level::Scenes; bool Level::TickEnabled = true; +float Level::StreamingFrameBudget = 0.3f; Delegate Level::ActorSpawned; Delegate Level::ActorDeleted; Delegate Level::ActorParentChanged; @@ -394,40 +433,22 @@ class LoadSceneAction : public SceneAction public: Guid SceneId; AssetReference SceneAsset; + SceneLoader Loader; - LoadSceneAction(const Guid& sceneId, JsonAsset* sceneAsset) + LoadSceneAction(const Guid& sceneId, JsonAsset* sceneAsset, bool async) + : Loader(async) { SceneId = sceneId; SceneAsset = sceneAsset; } - bool CanDo() const override + SceneResult Do(Context& context) override { - return SceneAsset == nullptr || SceneAsset->IsLoaded(); - } - - bool Do() const override - { - // Now to deserialize scene in a proper way we need to load scripting - if (!Scripting::IsEveryAssemblyLoaded()) - { - LOG(Error, "Scripts must be compiled without any errors in order to load a scene."); -#if USE_EDITOR - Platform::Error(TEXT("Scripts must be compiled without any errors in order to load a scene. Please fix it.")); -#endif - CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); - return true; - } - - // Load scene - if (Level::loadScene(SceneAsset)) - { - LOG(Error, "Failed to deserialize scene {0}", SceneId); - CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); - return true; - } - - return false; + if (SceneAsset == nullptr) + return SceneResult::Failed; + if (!SceneAsset->IsLoaded()) + return SceneResult::Wait; + return LevelImpl::loadScene(Loader, SceneAsset); } }; @@ -441,12 +462,12 @@ public: TargetScene = scene->GetID(); } - bool Do() const override + SceneResult Do(Context& context) override { auto scene = Level::FindScene(TargetScene); if (!scene) - return true; - return unloadScene(scene); + return SceneResult::Failed; + return unloadScene(scene) ? SceneResult::Failed : SceneResult::Success; } }; @@ -457,9 +478,9 @@ public: { } - bool Do() const override + SceneResult Do(Context& context) override { - return unloadScenes(); + return unloadScenes() ? SceneResult::Failed : SceneResult::Success; } }; @@ -475,14 +496,14 @@ public: PrettyJson = prettyJson; } - bool Do() const override + SceneResult Do(Context& context) override { if (saveScene(TargetScene)) { LOG(Error, "Failed to save scene {0}", TargetScene ? TargetScene->GetName() : String::Empty); - return true; + return SceneResult::Failed; } - return false; + return SceneResult::Success; } }; @@ -495,7 +516,7 @@ public: { } - bool Do() const override + SceneResult Do(Context& context) override { // Reloading scripts workflow: // - save scenes (to temporary files) @@ -556,7 +577,7 @@ public: { LOG(Error, "Failed to save scene '{0}' for scripts reload.", scenes[i].Name); CallSceneEvent(SceneEventType::OnSceneSaveError, scene, scene->GetID()); - return true; + return SceneResult::Failed; } CallSceneEvent(SceneEventType::OnSceneSaved, scene, scene->GetID()); } @@ -601,16 +622,17 @@ public: } if (document.HasParseError()) { - LOG(Error, "Failed to deserialize scene {0}. Result: {1}", scenes[i].Name, GetParseError_En(document.GetParseError())); - return true; + LOG(Error, "Failed to deserialize scene {0}. SceneResult: {1}", scenes[i].Name, GetParseError_En(document.GetParseError())); + return SceneResult::Failed; } // Load scene - if (Level::loadScene(document)) + SceneLoader loader; + if (LevelImpl::loadScene(loader, document) != SceneResult::Success) { LOG(Error, "Failed to deserialize scene {0}", scenes[i].Name); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, scenes[i].ID); - return true; + return SceneResult::Failed; } } scenes.Resize(0); @@ -619,7 +641,7 @@ public: LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast((DateTime::NowUTC() - startTime).GetTotalMilliseconds())); Level::ScriptsReloadEnd(); - return false; + return SceneResult::Success; } }; @@ -651,9 +673,9 @@ public: { } - bool Do() const override + SceneResult Do(Context& context) override { - return spawnActor(TargetActor, ParentActor); + return spawnActor(TargetActor, ParentActor) ? SceneResult::Failed : SceneResult::Success; } }; @@ -667,9 +689,9 @@ public: { } - bool Do() const override + SceneResult Do(Context& context) override { - return deleteActor(TargetActor); + return deleteActor(TargetActor) ? SceneResult::Failed : SceneResult::Success; } }; @@ -767,13 +789,29 @@ void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) void LevelImpl::flushActions() { - ScopeLock lock(_sceneActionsLocker); + // Calculate time budget for the streaming (relative to the game frame rate to scale across different devices) + SceneAction::Context context; + float targetFps = 60; + if (Time::UpdateFPS > ZeroTolerance) + targetFps = Time::UpdateFPS; + else if (Engine::GetFramesPerSecond() > 0) + targetFps = (float)Engine::GetFramesPerSecond(); + context.TimeBudget = Level::StreamingFrameBudget / targetFps; - while (_sceneActions.HasItems() && _sceneActions.First()->CanDo()) + // Runs actions in order + ScopeLock lock(_sceneActionsLocker); + for (int32 i = 0; i < _sceneActions.Count() && context.TimeBudget >= 0.0; i++) { - const auto action = _sceneActions.Dequeue(); - action->Do(); - Delete(action); + auto action = _sceneActions[0]; + Stopwatch time; + auto result = action->Do(context); + time.Stop(); + context.TimeBudget -= time.GetTotalSeconds(); + if (result != SceneResult::Wait) + { + _sceneActions.RemoveAtKeepOrder(i--); + Delete(action); + } } } @@ -823,25 +861,25 @@ bool LevelImpl::unloadScenes() return false; } -bool Level::loadScene(JsonAsset* sceneAsset) +SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset) { // Keep reference to the asset (prevent unloading during action) AssetReference ref = sceneAsset; if (sceneAsset == nullptr || sceneAsset->WaitForLoaded()) { LOG(Error, "Cannot load scene asset."); - return true; + return SceneResult::Failed; } - return loadScene(*sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath()); + return loadScene(loader, *sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath()); } -bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene) +SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene) { if (sceneData.IsInvalid()) { LOG(Error, "Missing scene data."); - return true; + return SceneResult::Failed; } PROFILE_MEM(Level); @@ -854,34 +892,48 @@ bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene) if (document.HasParseError()) { Log::JsonParseException(document.GetParseError(), document.GetErrorOffset()); - return true; + return SceneResult::Failed; } - ScopeLock lock(ScenesLock); - return loadScene(document, outScene); + ScopeLock lock(Level::ScenesLock); + return loadScene(loader, document, outScene); } -bool Level::loadScene(rapidjson_flax::Document& document, Scene** outScene) +SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene) { auto data = document.FindMember("Data"); if (data == document.MemberEnd()) { LOG(Error, "Missing Data member."); - return true; + return SceneResult::Failed; } const int32 saveEngineBuild = JsonTools::GetInt(document, "EngineBuild", 0); - return loadScene(data->value, saveEngineBuild, outScene); + return loadScene(loader, data->value, saveEngineBuild, outScene); } -bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath) +SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget) { PROFILE_CPU_NAMED("Level.LoadScene"); PROFILE_MEM(Level); - if (outScene) - *outScene = nullptr; #if USE_EDITOR ContentDeprecated::Clear(); #endif + SceneResult result = SceneResult::Success; + while ((!timeBudget || *timeBudget > 0.0f) && loader.Stage != SceneLoader::Loaded && result == SceneResult::Success) + { + Stopwatch time; + result = loader.Tick(data, engineBuild, outScene, assetPath, timeBudget); + time.Stop(); + const float delta = time.GetTotalSeconds(); + loader.TotalTime += delta; + if (timeBudget) + *timeBudget -= delta; + } + return result; +} + +SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget) +{ LOG(Info, "Loading scene..."); Stopwatch stopwatch; _lastSceneLoadTime = DateTime::Now(); @@ -900,19 +952,19 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou MessageBox::Show(TEXT("Failed to load scripts.\n\nCannot load scene without game script modules.\n\nSee logs for more info."), TEXT("Missing game modules"), MessageBoxButtons::OK, MessageBoxIcon::Error); } #endif - return true; + return SceneResult::Failed; } // Peek meta if (engineBuild < 6000) { LOG(Error, "Invalid serialized engine build."); - return true; + return SceneResult::Failed; } if (!data.IsArray()) { LOG(Error, "Invalid Data member."); - return true; + return SceneResult::Failed; } // Peek scene node value (it's the first actor serialized) @@ -920,16 +972,16 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou if (!sceneId.IsValid()) { LOG(Error, "Invalid scene id."); - return true; + return SceneResult::Failed; } auto modifier = Cache::ISerializeModifier.Get(); modifier->EngineBuild = engineBuild; // Skip is that scene is already loaded - if (FindScene(sceneId) != nullptr) + if (Level::FindScene(sceneId) != nullptr) { LOG(Info, "Scene {0} is already loaded.", sceneId); - return false; + return SceneResult::Failed; } // Create scene actor @@ -958,7 +1010,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou SceneObject** objects = sceneObjects->Get(); if (context.Async) { - ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) + Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) JobSystem::Execute([&](int32 i) { PROFILE_MEM(Level); @@ -979,7 +1031,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou else SceneObjectsFactory::HandleObjectDeserializationError(stream); }, dataCount - 1); - ScenesLock.Lock(); + Level::ScenesLock.Lock(); } else { @@ -1015,7 +1067,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou // TODO: - add _loadNoAsync flag to SceneObject or Actor to handle non-async loading for those types (eg. UIControl/UICanvas) if (context.Async) { - ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) + Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) #if USE_EDITOR volatile int64 deprecated = 0; #endif @@ -1039,7 +1091,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou if (deprecated != 0) ContentDeprecated::Mark(); #endif - ScenesLock.Lock(); + Level::ScenesLock.Lock(); } else { @@ -1114,13 +1166,15 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou { PROFILE_CPU_NAMED("BeginPlay"); - ScopeLock lock(ScenesLock); - Scenes.Add(scene); + ScopeLock lock(Level::ScenesLock); + Level::Scenes.Add(scene); SceneBeginData beginData; scene->BeginPlay(&beginData); beginData.OnDone(); } + Stage = Loaded; + // Fire event CallSceneEvent(SceneEventType::OnSceneLoaded, scene, sceneId); @@ -1150,7 +1204,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou } #endif - return false; + return SceneResult::Success; } bool LevelImpl::saveScene(Scene* scene) @@ -1271,7 +1325,8 @@ bool LevelImpl::saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, bool Level::SaveScene(Scene* scene, bool prettyJson) { ScopeLock lock(_sceneActionsLocker); - return SaveSceneAction(scene, prettyJson).Do(); + SceneAction::Context context; + return SaveSceneAction(scene, prettyJson).Do(context) != SceneResult::Success; } bool Level::SaveSceneToBytes(Scene* scene, rapidjson_flax::StringBuffer& outData, bool prettyJson) @@ -1317,9 +1372,10 @@ void Level::SaveSceneAsync(Scene* scene) bool Level::SaveAllScenes() { ScopeLock lock(_sceneActionsLocker); + SceneAction::Context context; for (int32 i = 0; i < Scenes.Count(); i++) { - if (SaveSceneAction(Scenes[i]).Do()) + if (SaveSceneAction(Scenes[i]).Do(context) != SceneResult::Success) return true; } return false; @@ -1369,7 +1425,8 @@ bool Level::LoadScene(const Guid& id) // Load scene ScopeLock lock(ScenesLock); - if (loadScene(sceneAsset)) + SceneLoader loader; + if (loadScene(loader, sceneAsset) != SceneResult::Success) { LOG(Error, "Failed to deserialize scene {0}", id); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, id); @@ -1381,7 +1438,8 @@ bool Level::LoadScene(const Guid& id) Scene* Level::LoadSceneFromBytes(const BytesContainer& data) { Scene* scene = nullptr; - if (loadScene(data, &scene)) + SceneLoader loader; + if (loadScene(loader, data, &scene) != SceneResult::Success) { LOG(Error, "Failed to deserialize scene from bytes"); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, Guid::Empty); @@ -1391,7 +1449,6 @@ Scene* Level::LoadSceneFromBytes(const BytesContainer& data) bool Level::LoadSceneAsync(const Guid& id) { - // Check ID if (!id.IsValid()) { Log::ArgumentException(); @@ -1407,7 +1464,7 @@ bool Level::LoadSceneAsync(const Guid& id) } ScopeLock lock(_sceneActionsLocker); - _sceneActions.Enqueue(New(id, sceneAsset)); + _sceneActions.Enqueue(New(id, sceneAsset, true)); return false; } diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index f07a20c51..597bc0a87 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -48,7 +48,12 @@ public: /// /// True if game objects (actors and scripts) can receive a tick during engine Update/LateUpdate/FixedUpdate events. Can be used to temporarily disable gameplay logic updating. /// - API_FIELD() static bool TickEnabled; + API_FIELD(Attributes="DebugCommand") static bool TickEnabled; + + /// + /// Fraction of the frame budget to limit time spent on levels streaming. For example, value of 0.3 means that 30% of frame time can be spent on levels loading within a single frame (eg. 0.3 at 60fps is 4.8ms budget). + /// + API_FIELD(Attributes="DebugCommand") static float StreamingFrameBudget; public: /// @@ -547,10 +552,4 @@ private: }; static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b); - - // All loadScene assume that ScenesLock has been taken by the calling thread - static bool loadScene(JsonAsset* sceneAsset); - static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr); - static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr); - static bool loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, const String* assetPath = nullptr); }; diff --git a/Source/Engine/Level/Scene/Scene.h b/Source/Engine/Level/Scene/Scene.h index a34ebd592..f8f40b05a 100644 --- a/Source/Engine/Level/Scene/Scene.h +++ b/Source/Engine/Level/Scene/Scene.h @@ -19,6 +19,7 @@ API_CLASS() class FLAXENGINE_API Scene : public Actor { friend class Level; friend class ReloadScriptsAction; + friend class SceneLoader; DECLARE_SCENE_OBJECT(Scene); /// From d6eb647d5991d14fa5a4c56da52f84ffee753c5c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Jun 2025 14:33:47 +0200 Subject: [PATCH 43/84] Optimize async scene loading to run in separate stages with time-slicing --- Source/Engine/Core/Cache.cpp | 2 +- Source/Engine/Core/Cache.h | 3 +- Source/Engine/Level/Level.cpp | 504 ++++++++++++++++++++++------------ 3 files changed, 328 insertions(+), 181 deletions(-) diff --git a/Source/Engine/Core/Cache.cpp b/Source/Engine/Core/Cache.cpp index b562465c0..315a93775 100644 --- a/Source/Engine/Core/Cache.cpp +++ b/Source/Engine/Core/Cache.cpp @@ -3,7 +3,7 @@ #include "Cache.h" #include "FlaxEngine.Gen.h" -CollectionPoolCache Cache::ISerializeModifier; +Cache::ISerializeModifierCache Cache::ISerializeModifier; void Cache::ISerializeModifierClearCallback(::ISerializeModifier* obj) { diff --git a/Source/Engine/Core/Cache.h b/Source/Engine/Core/Cache.h index c0a8e3ac4..1b8d95910 100644 --- a/Source/Engine/Core/Cache.h +++ b/Source/Engine/Core/Cache.h @@ -15,11 +15,12 @@ public: static void ISerializeModifierClearCallback(ISerializeModifier* obj); public: + typedef CollectionPoolCache ISerializeModifierCache; /// /// Gets the ISerializeModifier lookup cache. Safe allocation, per thread, uses caching. /// - static CollectionPoolCache ISerializeModifier; + static ISerializeModifierCache ISerializeModifier; public: diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index d3f1ccba3..881277c5e 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -118,27 +118,79 @@ struct ScriptsReloadObject class SceneLoader { public: - enum Stages + struct Args { - Init, + rapidjson_flax::Value& Data; + const String* AssetPath; + int32 EngineBuild; + float TimeBudget; + }; + + enum class Stages + { + Begin, Spawn, SetupPrefabs, + SyncNewPrefabs, Deserialize, + SyncPrefabs, SetupTransforms, + Initialize, BeginPlay, + End, Loaded, - } Stage = Init; + } Stage = Stages::Begin; + bool AsyncLoad; bool AsyncJobs; + Guid SceneId = Guid::Empty; + Scene* Scene = nullptr; float TotalTime = 0.0f; + uint64 StartFrame; + + // Cache data + ISerializeModifier* Modifier = nullptr; + ActorsCache::SceneObjectsListType* SceneObjects = nullptr; + Array InjectedSceneChildren; + SceneObjectsFactory::Context Context; + SceneObjectsFactory::PrefabSyncData* PrefabSyncData = nullptr; SceneLoader(bool asyncLoad = false) - : AsyncLoad(true) + : AsyncLoad(asyncLoad) , AsyncJobs(JobSystem::GetThreadsCount() > 1) + , Modifier(Cache::ISerializeModifier.GetUnscoped()) + , Context(Modifier) { } - SceneResult Tick(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget); + ~SceneLoader() + { + if (PrefabSyncData) + Delete(PrefabSyncData); + if (SceneObjects) + ActorsCache::SceneObjectsListCache.Put(SceneObjects); + if (Modifier) + Cache::ISerializeModifier.Put(Modifier); + } + + NON_COPYABLE(SceneLoader); + + FORCE_INLINE void NextStage() + { + Stage = (Stages)((uint8)Stage + 1); + } + + SceneResult Tick(Args& args); + SceneResult OnBegin(Args& args); + SceneResult OnSpawn(Args& args); + SceneResult OnSetupPrefabs(Args& args); + SceneResult OnSyncNewPrefabs(Args& args); + SceneResult OnDeserialize(Args& args); + SceneResult OnSyncPrefabs(Args& args); + SceneResult OnSetupTransforms(Args& args); + SceneResult OnInitialize(Args& args); + SceneResult OnBeginPlay(Args& args); + SceneResult OnEnd(Args& args); }; namespace LevelImpl @@ -153,9 +205,9 @@ namespace LevelImpl void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId); void flushActions(); - SceneResult loadScene(SceneLoader& loader, JsonAsset* sceneAsset); - SceneResult loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene = nullptr); - SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene = nullptr); + SceneResult loadScene(SceneLoader& loader, JsonAsset* sceneAsset, float* timeBudget = nullptr); + SceneResult loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene = nullptr, float* timeBudget = nullptr); + SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene = nullptr, float* timeBudget = nullptr); SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, const String* assetPath = nullptr, float* timeBudget = nullptr); bool unloadScene(Scene* scene); bool unloadScenes(); @@ -448,7 +500,7 @@ public: return SceneResult::Failed; if (!SceneAsset->IsLoaded()) return SceneResult::Wait; - return LevelImpl::loadScene(Loader, SceneAsset); + return LevelImpl::loadScene(Loader, SceneAsset, &context.TimeBudget); } }; @@ -797,6 +849,16 @@ void LevelImpl::flushActions() else if (Engine::GetFramesPerSecond() > 0) targetFps = (float)Engine::GetFramesPerSecond(); context.TimeBudget = Level::StreamingFrameBudget / targetFps; +#if USE_EDITOR + // Throttle up in Editor + context.TimeBudget *= Editor::IsPlayMode ? 1.2f : 2.0f; +#endif +#if BUILD_DEBUG + // Throttle up in Debug + context.TimeBudget *= 1.2f; +#endif + if (context.TimeBudget <= ZeroTolerance) + context.TimeBudget = MAX_float; // Runs actions in order ScopeLock lock(_sceneActionsLocker); @@ -861,7 +923,7 @@ bool LevelImpl::unloadScenes() return false; } -SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset) +SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset, float* timeBudget) { // Keep reference to the asset (prevent unloading during action) AssetReference ref = sceneAsset; @@ -871,10 +933,10 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset) return SceneResult::Failed; } - return loadScene(loader, *sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath()); + return loadScene(loader, *sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath(), timeBudget); } -SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene) +SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene, float* timeBudget) { if (sceneData.IsInvalid()) { @@ -896,10 +958,10 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& scen } ScopeLock lock(Level::ScenesLock); - return loadScene(loader, document, outScene); + return loadScene(loader, document, outScene, timeBudget); } -SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene) +SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene, float* timeBudget) { auto data = document.FindMember("Data"); if (data == document.MemberEnd()) @@ -908,7 +970,7 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document& return SceneResult::Failed; } const int32 saveEngineBuild = JsonTools::GetInt(document, "EngineBuild", 0); - return loadScene(loader, data->value, saveEngineBuild, outScene); + return loadScene(loader, data->value, saveEngineBuild, outScene, nullptr, timeBudget); } SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget) @@ -919,27 +981,64 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& dat ContentDeprecated::Clear(); #endif SceneResult result = SceneResult::Success; - while ((!timeBudget || *timeBudget > 0.0f) && loader.Stage != SceneLoader::Loaded && result == SceneResult::Success) + float timeLeft = timeBudget ? *timeBudget : MAX_float; + SceneLoader::Args args = { data, assetPath, engineBuild, timeLeft }; + while (timeLeft > 0.0f && loader.Stage != SceneLoader::Stages::Loaded) { Stopwatch time; - result = loader.Tick(data, engineBuild, outScene, assetPath, timeBudget); + result = loader.Tick(args); time.Stop(); const float delta = time.GetTotalSeconds(); loader.TotalTime += delta; - if (timeBudget) - *timeBudget -= delta; + timeLeft -= delta; + if (timeLeft < 0.0f && result == SceneResult::Success) + { + result = SceneResult::Wait; + break; + } } + if (outScene) + *outScene = loader.Scene; return result; } -SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget) +SceneResult SceneLoader::Tick(Args& args) { - LOG(Info, "Loading scene..."); - Stopwatch stopwatch; - _lastSceneLoadTime = DateTime::Now(); + switch (Stage) + { + case Stages::Begin: + return OnBegin(args); + case Stages::Spawn: + return OnSpawn(args); + case Stages::SetupPrefabs: + return OnSetupPrefabs(args); + case Stages::SyncNewPrefabs: + return OnSyncNewPrefabs(args); + case Stages::Deserialize: + return OnDeserialize(args); + case Stages::SyncPrefabs: + return OnSyncPrefabs(args); + case Stages::Initialize: + return OnInitialize(args); + case Stages::SetupTransforms: + return OnSetupTransforms(args); + case Stages::BeginPlay: + return OnBeginPlay(args); + case Stages::End: + return OnEnd(args); + default: + return SceneResult::Failed; + } +} - // Here whole scripting backend should be loaded for current project - // Later scripts will setup attached scripts and restore initial vars +SceneResult SceneLoader::OnBegin(Args& args) +{ + PROFILE_CPU_NAMED("Begin"); + LOG(Info, "Loading scene..."); + _lastSceneLoadTime = DateTime::Now(); + StartFrame = Engine::UpdateCount; + + // Scripting backend should be loaded for the current project before loading scene if (!Scripting::HasGameModulesLoaded()) { LOG(Error, "Cannot load scene without game modules loaded."); @@ -956,163 +1055,186 @@ SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Sc } // Peek meta - if (engineBuild < 6000) + if (args.EngineBuild < 6000) { LOG(Error, "Invalid serialized engine build."); return SceneResult::Failed; } - if (!data.IsArray()) + if (!args.Data.IsArray()) { LOG(Error, "Invalid Data member."); return SceneResult::Failed; } + Modifier->EngineBuild = args.EngineBuild; // Peek scene node value (it's the first actor serialized) - auto sceneId = JsonTools::GetGuid(data[0], "ID"); - if (!sceneId.IsValid()) + SceneId = JsonTools::GetGuid(args.Data[0], "ID"); + if (!SceneId.IsValid()) { LOG(Error, "Invalid scene id."); return SceneResult::Failed; } - auto modifier = Cache::ISerializeModifier.Get(); - modifier->EngineBuild = engineBuild; // Skip is that scene is already loaded - if (Level::FindScene(sceneId) != nullptr) + if (Level::FindScene(SceneId) != nullptr) { - LOG(Info, "Scene {0} is already loaded.", sceneId); + LOG(Info, "Scene {0} is already loaded.", SceneId); return SceneResult::Failed; } // Create scene actor - // Note: the first object in the scene file data is a Scene Actor - auto scene = New(ScriptingObjectSpawnParams(sceneId, Scene::TypeInitializer)); - scene->RegisterObject(); - scene->Deserialize(data[0], modifier.Value); + Scene = New<::Scene>(ScriptingObjectSpawnParams(SceneId, Scene::TypeInitializer)); + Scene->RegisterObject(); + Scene->Deserialize(args.Data[0], Modifier); // Fire event - CallSceneEvent(SceneEventType::OnSceneLoading, scene, sceneId); + CallSceneEvent(SceneEventType::OnSceneLoading, Scene, SceneId); + + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnSpawn(Args& args) +{ + PROFILE_CPU_NAMED("Spawn"); // Get any injected children of the scene. - Array injectedSceneChildren = scene->Children; + InjectedSceneChildren = Scene->Children; - // Loaded scene objects list - CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); - const int32 dataCount = (int32)data.Size(); - sceneObjects->Resize(dataCount); - sceneObjects->At(0) = scene; + // Allocate scene objects list + SceneObjects = ActorsCache::SceneObjectsListCache.GetUnscoped(); + const int32 dataCount = (int32)args.Data.Size(); + SceneObjects->Resize(dataCount); + SceneObjects->At(0) = Scene; + AsyncJobs &= dataCount > 10; // Spawn all scene objects - SceneObjectsFactory::Context context(modifier.Value); - context.Async = JobSystem::GetThreadsCount() > 1 && dataCount > 10; + Context.Async = AsyncJobs; + SceneObject** objects = SceneObjects->Get(); + if (Context.Async) { - PROFILE_CPU_NAMED("Spawn"); - SceneObject** objects = sceneObjects->Get(); - if (context.Async) + Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) + JobSystem::Execute([&](int32 i) { - Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) - JobSystem::Execute([&](int32 i) + PROFILE_MEM(Level); + i++; // Start from 1. at index [0] was scene + auto& stream = args.Data[i]; + auto obj = SceneObjectsFactory::Spawn(Context, stream); + objects[i] = obj; + if (obj) { - PROFILE_MEM(Level); - i++; // Start from 1. at index [0] was scene - auto& stream = data[i]; - auto obj = SceneObjectsFactory::Spawn(context, stream); - objects[i] = obj; - if (obj) - { - if (!obj->IsRegistered()) - obj->RegisterObject(); -#if USE_EDITOR - // Auto-create C# objects for all actors in Editor during scene load when running in async (so main thread already has all of them) - if (!obj->GetManagedInstance()) - obj->CreateManaged(); -#endif - } - else - SceneObjectsFactory::HandleObjectDeserializationError(stream); - }, dataCount - 1); - Level::ScenesLock.Lock(); - } - else - { - for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene - { - auto& stream = data[i]; - auto obj = SceneObjectsFactory::Spawn(context, stream); - sceneObjects->At(i) = obj; - if (obj) + if (!obj->IsRegistered()) obj->RegisterObject(); - else - SceneObjectsFactory::HandleObjectDeserializationError(stream); +#if USE_EDITOR + // Auto-create C# objects for all actors in Editor during scene load when running in async (so main thread already has all of them) + if (!obj->GetManagedInstance()) + obj->CreateManaged(); +#endif } + else + SceneObjectsFactory::HandleObjectDeserializationError(stream); + }, dataCount - 1); + Level::ScenesLock.Lock(); + } + else + { + for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene + { + auto& stream = args.Data[i]; + auto obj = SceneObjectsFactory::Spawn(Context, stream); + objects[i] = obj; + if (obj) + obj->RegisterObject(); + else + SceneObjectsFactory::HandleObjectDeserializationError(stream); } } - // Capture prefab instances in a scene to restore any missing objects (eg. newly added objects to prefab that are missing in scene file) - SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value); - SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData); - // TODO: resave and force sync scenes during game cooking so this step could be skipped in game - SceneObjectsFactory::SynchronizeNewPrefabInstances(context, prefabSyncData); + NextStage(); + return SceneResult::Success; +} - // /\ all above this has to be done on an any thread - // \/ all below this has to be done on multiple threads at once +SceneResult SceneLoader::OnSetupPrefabs(Args& args) +{ + // Capture prefab instances in a scene to restore any missing objects (eg. newly added objects to prefab that are missing in scene file) + PrefabSyncData = New(*SceneObjects, args.Data, Modifier); + SceneObjectsFactory::SetupPrefabInstances(Context, *PrefabSyncData); + + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnSyncNewPrefabs(Args& args) +{ + // Sync the new prefab instances by spawning missing objects that were added to prefab but were not saved in a scene + // TODO: resave and force sync scenes during game cooking so this step could be skipped in game + SceneObjectsFactory::SynchronizeNewPrefabInstances(Context, *PrefabSyncData); + + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnDeserialize(Args& args) +{ + PROFILE_CPU_NAMED("Deserialize"); + const int32 dataCount = (int32)args.Data.Size(); + SceneObject** objects = SceneObjects->Get(); + bool wasAsync = Context.Async; + Context.Async = false; // TODO: before doing full async for scene objects fix: + // TODO: - fix Actor's Scripts and Children order when loading objects data out of order via async jobs + // TODO: - add _loadNoAsync flag to SceneObject or Actor to handle non-async loading for those types (eg. UIControl/UICanvas) // Load all scene objects + if (Context.Async) { - PROFILE_CPU_NAMED("Deserialize"); - SceneObject** objects = sceneObjects->Get(); - bool wasAsync = context.Async; - context.Async = false; // TODO: before doing full async for scene objects fix: - // TODO: - fix Actor's Scripts and Children order when loading objects data out of order via async jobs - // TODO: - add _loadNoAsync flag to SceneObject or Actor to handle non-async loading for those types (eg. UIControl/UICanvas) - if (context.Async) + Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) +#if USE_EDITOR + volatile int64 deprecated = 0; +#endif + JobSystem::Execute([&](int32 i) { - Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) -#if USE_EDITOR - volatile int64 deprecated = 0; -#endif - JobSystem::Execute([&](int32 i) + i++; // Start from 1. at index [0] was scene + auto obj = objects[i]; + if (obj) { - i++; // Start from 1. at index [0] was scene - auto obj = objects[i]; - if (obj) - { - auto& idMapping = Scripting::ObjectsLookupIdMapping.Get(); - idMapping = &context.GetModifier()->IdsMapping; - SceneObjectsFactory::Deserialize(context, obj, data[i]); + auto& idMapping = Scripting::ObjectsLookupIdMapping.Get(); + idMapping = &Context.GetModifier()->IdsMapping; + SceneObjectsFactory::Deserialize(Context, obj, args.Data[i]); #if USE_EDITOR - if (ContentDeprecated::Clear()) - Platform::InterlockedIncrement(&deprecated); + if (ContentDeprecated::Clear()) + Platform::InterlockedIncrement(&deprecated); #endif - idMapping = nullptr; - } - }, dataCount - 1); -#if USE_EDITOR - if (deprecated != 0) - ContentDeprecated::Mark(); -#endif - Level::ScenesLock.Lock(); - } - else - { - Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); - for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene - { - auto& objData = data[i]; - auto obj = objects[i]; - if (obj) - SceneObjectsFactory::Deserialize(context, obj, objData); + idMapping = nullptr; } - Scripting::ObjectsLookupIdMapping.Set(nullptr); - } - context.Async = wasAsync; + }, dataCount - 1); +#if USE_EDITOR + if (deprecated != 0) + ContentDeprecated::Mark(); +#endif + Level::ScenesLock.Lock(); } + else + { + Scripting::ObjectsLookupIdMapping.Set(&Modifier->IdsMapping); + for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene + { + auto& objData = args.Data[i]; + auto obj = objects[i]; + if (obj) + SceneObjectsFactory::Deserialize(Context, obj, objData); + } + Scripting::ObjectsLookupIdMapping.Set(nullptr); + } + Context.Async = wasAsync; - // /\ all above this has to be done on multiple threads at once - // \/ all below this has to be done on an any thread + NextStage(); + return SceneResult::Success; +} +SceneResult SceneLoader::OnSyncPrefabs(Args& args) +{ // Add injected children of scene (via OnSceneLoading) into sceneObjects to be initialized - for (auto child : injectedSceneChildren) + for (auto child : InjectedSceneChildren) { Array injectedSceneObjects; injectedSceneObjects.Add(child); @@ -1121,71 +1243,93 @@ SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Sc { if (!o->IsRegistered()) o->RegisterObject(); - sceneObjects->Add(o); + SceneObjects->Add(o); } } // Synchronize prefab instances (prefab may have objects removed or reordered so deserialized instances need to synchronize with it) // TODO: resave and force sync scenes during game cooking so this step could be skipped in game - SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); + SceneObjectsFactory::SynchronizePrefabInstances(Context, *PrefabSyncData); - // Cache transformations - { - PROFILE_CPU_NAMED("Cache Transform"); + NextStage(); + return SceneResult::Success; +} - scene->OnTransformChanged(); - } +SceneResult SceneLoader::OnSetupTransforms(Args& args) +{ + // Cache actor transformations + PROFILE_CPU_NAMED("SetupTransforms"); + Scene->OnTransformChanged(); + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnInitialize(Args& args) +{ // Initialize scene objects + PROFILE_CPU_NAMED("Initialize"); + ASSERT_LOW_LAYER(IsInMainThread()); + SceneObject** objects = SceneObjects->Get(); + for (int32 i = 0; i < SceneObjects->Count(); i++) { - PROFILE_CPU_NAMED("Initialize"); - - SceneObject** objects = sceneObjects->Get(); - for (int32 i = 0; i < sceneObjects->Count(); i++) + SceneObject* obj = objects[i]; + if (obj) { - SceneObject* obj = objects[i]; - if (obj) - { - obj->Initialize(); + obj->Initialize(); - // Delete objects without parent - if (i != 0 && obj->GetParent() == nullptr) - { - LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); - obj->DeleteObject(); - } + // Delete objects without parent + if (i != 0 && obj->GetParent() == nullptr) + { + LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); + obj->DeleteObject(); } } - prefabSyncData.InitNewObjects(); } + PrefabSyncData->InitNewObjects(); - // /\ all above this has to be done on an any thread - // \/ all below this has to be done on a main thread + NextStage(); + return SceneResult::Success; +} - // Link scene and call init - { - PROFILE_CPU_NAMED("BeginPlay"); +SceneResult SceneLoader::OnBeginPlay(Args& args) +{ + PROFILE_CPU_NAMED("BeginPlay"); + ASSERT_LOW_LAYER(IsInMainThread()); - ScopeLock lock(Level::ScenesLock); - Level::Scenes.Add(scene); - SceneBeginData beginData; - scene->BeginPlay(&beginData); - beginData.OnDone(); - } + // Link scene + ScopeLock lock(Level::ScenesLock); + Level::Scenes.Add(Scene); - Stage = Loaded; + // TODO: prototype time-slicing with load-balancing for Begin Play: + // TODO: - collect all actors to enable + // TODO: - invoke in order OnBeginPlay -> Child Actors Begin -> Child Scripts Begin -> OnEnable for each actor + // TODO: - consider not drawing level until it's fully loaded (other engine systems should respect this too?) + // TODO: - consider refactoring Joints creation maybe? to get rid of SceneBeginData + + // Start the game for scene objects + SceneBeginData beginData; + Scene->BeginPlay(&beginData); + beginData.OnDone(); + + NextStage(); + return SceneResult::Success; +} + +SceneResult SceneLoader::OnEnd(Args& args) +{ + PROFILE_CPU_NAMED("End"); + Stopwatch time; // Fire event - CallSceneEvent(SceneEventType::OnSceneLoaded, scene, sceneId); + CallSceneEvent(SceneEventType::OnSceneLoaded, Scene, SceneId); - stopwatch.Stop(); - LOG(Info, "Scene loaded in {0}ms", stopwatch.GetMilliseconds()); - if (outScene) - *outScene = scene; + time.Stop(); + LOG(Info, "Scene loaded in {}ms ({} frames)", (int32)((TotalTime + time.GetTotalSeconds()) * 1000.0), Engine::UpdateCount - StartFrame); #if USE_EDITOR // Resave assets that use deprecated data format - for (auto& e : context.DeprecatedPrefabs) + for (auto& e : Context.DeprecatedPrefabs) { AssetReference prefab = e.Item; LOG(Info, "Resaving asset '{}' that uses deprecated data format", prefab->GetPath()); @@ -1194,16 +1338,17 @@ SceneResult SceneLoader::Tick(rapidjson_flax::Value& data, int32 engineBuild, Sc LOG(Error, "Failed to resave asset '{}'", prefab->GetPath()); } } - if (ContentDeprecated::Clear() && assetPath) + if (ContentDeprecated::Clear() && args.AssetPath) { - LOG(Info, "Resaving asset '{}' that uses deprecated data format", *assetPath); - if (saveScene(scene, *assetPath)) + LOG(Info, "Resaving asset '{}' that uses deprecated data format", *args.AssetPath); + if (saveScene(Scene, *args.AssetPath)) { - LOG(Error, "Failed to resave asset '{}'", *assetPath); + LOG(Error, "Failed to resave asset '{}'", *args.AssetPath); } } #endif + NextStage(); return SceneResult::Success; } @@ -1732,8 +1877,9 @@ Array Level::GetScripts(const MClass* type, Actor* root) const bool isInterface = type->IsInterface(); if (root) ::GetScripts(type, isInterface, root, result); - else for (int32 i = 0; i < Scenes.Count(); i++) - ::GetScripts(type, isInterface, Scenes[i], result); + else + for (int32 i = 0; i < Scenes.Count(); i++) + ::GetScripts(type, isInterface, Scenes[i], result); return result; } From e9835766bc3efb25db9b27beda2160ba9b139b21 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Jun 2025 14:56:43 +0200 Subject: [PATCH 44/84] Add red color to Tracy profiler zones that cause CPU waiting to improve profiling --- Source/Engine/Content/Asset.cpp | 3 +++ Source/Engine/Content/Content.cpp | 2 ++ .../Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp | 6 +++++- .../Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp | 5 +++-- .../Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp | 3 +++ Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 1 + Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp | 1 + Source/Engine/Profiler/ProfilerCPU.h | 3 +++ Source/Engine/Threading/JobSystem.cpp | 1 + Source/Engine/Threading/Task.cpp | 2 ++ 10 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 86801b078..5a9e62993 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -494,6 +494,9 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const } PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); + const StringView path(GetPath()); + ZoneText(*path, path.Length()); Content::WaitForTask(loadingTask, timeoutInMilliseconds); diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 4ba6cbc44..2178579cc 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -1128,6 +1128,8 @@ void Content::WaitForTask(ContentLoadTask* loadingTask, double timeoutInMillisec localQueue.Clear(); } + PROFILE_CPU_NAMED("Inline"); + ZoneColor(0xffaaaaaa); thread->Run(tmp); } else diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp index 7a106d377..3856a7cd0 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp @@ -3,11 +3,12 @@ #if GRAPHICS_API_DIRECTX11 #include "GPUSwapChainDX11.h" +#include "GPUContextDX11.h" #include "Engine/Platform/Window.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" -#include "GPUContextDX11.h" GPUSwapChainDX11::GPUSwapChainDX11(GPUDeviceDX11* device, Window* window) : GPUResourceDX11(device, StringView::Empty) @@ -140,6 +141,9 @@ GPUTextureView* GPUSwapChainDX11::GetBackBufferView() void GPUSwapChainDX11::Present(bool vsync) { + PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); + // Present frame ASSERT(_swapChain); UINT presentFlags = 0; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp index 2488480af..81fef4965 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp @@ -6,6 +6,7 @@ #include "GPUDeviceDX12.h" #include "Engine/Threading/Threading.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Profiler/ProfilerCPU.h" FenceDX12::FenceDX12(GPUDeviceDX12* device) : _currentValue(1) @@ -64,12 +65,12 @@ void FenceDX12::WaitCPU(uint64 value) { if (IsFenceComplete(value)) return; - + PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); ScopeLock lock(_locker); _fence->SetEventOnCompletion(value, _event); WaitForSingleObject(_event, INFINITE); - _lastCompletedValue = _fence->GetCompletedValue(); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp index fa6dfa881..83ecf020d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp @@ -6,6 +6,7 @@ #include "GPUContextDX12.h" #include "../IncludeDirectXHeaders.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" void BackBufferDX12::Setup(GPUSwapChainDX12* window, ID3D12Resource* backbuffer) @@ -364,6 +365,8 @@ void GPUSwapChainDX12::End(RenderTask* task) void GPUSwapChainDX12::Present(bool vsync) { + PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); #if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE ID3D12Resource* backBuffer = _backBuffers[_currentFrameIndex].GetResource(); D3D12XBOX_PRESENT_PLANE_PARAMETERS planeParameters = {}; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 73eb90755..f2d0aad7d 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -2094,6 +2094,7 @@ void GPUDeviceVulkan::WaitForGPU() if (Device != VK_NULL_HANDLE) { PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); VALIDATE_VULKAN_RESULT(vkDeviceWaitIdle(Device)); } } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index 801ba1fc1..21971c5ca 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -553,6 +553,7 @@ void GPUSwapChainVulkan::Present(bool vsync) if (_acquiredImageIndex == -1) return; PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); // Ensure that backbuffer has been acquired before presenting it to the window const auto backBuffer = (GPUTextureViewVulkan*)GetBackBufferView(); diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index e8d0523f4..77ecfe7b2 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -412,3 +412,6 @@ struct TIsPODType #define PROFILE_CPU_ACTOR(actor) #endif + +// CPU-wait zones can be marked with red color for better readability +#define TracyWaitZoneColor 0xba1904 diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index 612584c40..e90a2e847 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -385,6 +385,7 @@ void JobSystem::Wait(int64 label) { #if JOB_SYSTEM_ENABLED PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); while (Platform::AtomicRead(&ExitFlag) == 0) { diff --git a/Source/Engine/Threading/Task.cpp b/Source/Engine/Threading/Task.cpp index 601079e85..911e1e3e8 100644 --- a/Source/Engine/Threading/Task.cpp +++ b/Source/Engine/Threading/Task.cpp @@ -40,6 +40,7 @@ void Task::Cancel() bool Task::Wait(double timeoutMilliseconds) const { PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); const double startTime = Platform::GetTimeSeconds(); // TODO: no active waiting! use a semaphore! @@ -76,6 +77,7 @@ bool Task::Wait(double timeoutMilliseconds) const bool Task::WaitAll(const Span& tasks, double timeoutMilliseconds) { PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); for (int32 i = 0; i < tasks.Length(); i++) { if (tasks[i]->Wait()) From 5b6859a66f55fb4472a795e2c267dca844f760e1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Jun 2025 18:40:06 +0200 Subject: [PATCH 45/84] Add time slicing to Deserialization stage of async scenes loading to avoid hitching #3261 --- Source/Engine/Level/Level.cpp | 61 ++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 881277c5e..678eada1f 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -114,6 +114,19 @@ struct ScriptsReloadObject #endif +// Small utility for dividing the iterative work over data set that can run in equal slicer limited by time. +struct TimeSlicer +{ + int32 Index = -1; + int32 Count = 0; + double TimeBudget; + double StartTime; + + void BeginSync(float timeBudget, int32 count, int32 startIndex = 0); + bool StepSync(); + SceneResult End(); +}; + // Async map loading utility for state tracking and synchronization of various load stages. class SceneLoader { @@ -154,6 +167,7 @@ public: Array InjectedSceneChildren; SceneObjectsFactory::Context Context; SceneObjectsFactory::PrefabSyncData* PrefabSyncData = nullptr; + TimeSlicer StageSlicer; SceneLoader(bool asyncLoad = false) : AsyncLoad(asyncLoad) @@ -1002,6 +1016,38 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& dat return result; } +void TimeSlicer::BeginSync(float timeBudget, int32 count, int32 startIndex) +{ + if (Index == -1) + { + // Starting + Index = startIndex; + Count = count; + } + TimeBudget = (double)timeBudget; + StartTime = Platform::GetTimeSeconds(); +} + +bool TimeSlicer::StepSync() +{ + Index++; + double time = Platform::GetTimeSeconds(); + double dt = time - StartTime; + return dt >= TimeBudget; +} + +SceneResult TimeSlicer::End() +{ + if (Index >= Count) + { + // Finished + *this = TimeSlicer(); + return SceneResult::Success; + } + + return SceneResult::Wait; +} + SceneResult SceneLoader::Tick(Args& args) { switch (Stage) @@ -1216,19 +1262,24 @@ SceneResult SceneLoader::OnDeserialize(Args& args) else { Scripting::ObjectsLookupIdMapping.Set(&Modifier->IdsMapping); - for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene + StageSlicer.BeginSync(args.TimeBudget, dataCount, 1); // start from 1. at index [0] was scene + while (StageSlicer.Index < StageSlicer.Count) { - auto& objData = args.Data[i]; - auto obj = objects[i]; + auto& objData = args.Data[StageSlicer.Index]; + auto obj = objects[StageSlicer.Index]; if (obj) SceneObjectsFactory::Deserialize(Context, obj, objData); + if (StageSlicer.StepSync()) + break; } Scripting::ObjectsLookupIdMapping.Set(nullptr); } Context.Async = wasAsync; - NextStage(); - return SceneResult::Success; + auto result = StageSlicer.End(); + if (result != SceneResult::Wait) + NextStage(); + return result; } SceneResult SceneLoader::OnSyncPrefabs(Args& args) From 8ec138399af7e5a703a1e9b8b946f26dcd0c8473 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Jun 2025 18:40:35 +0200 Subject: [PATCH 46/84] Add higher level streaming time budget in frame based on idle time --- Source/Engine/Engine/Engine.cpp | 5 +++++ Source/Engine/Level/Level.cpp | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 5607432c2..4ea18a85c 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -76,6 +76,7 @@ FatalErrorType Engine::FatalError = FatalErrorType::None; bool Engine::IsRequestingExit = false; int32 Engine::ExitCode = 0; Window* Engine::MainWindow = nullptr; +double EngineIdleTime = 0; int32 Engine::Main(const Char* cmdLine) { @@ -190,7 +191,10 @@ int32 Engine::Main(const Char* cmdLine) if (timeToTick > 0.002) { PROFILE_CPU_NAMED("Idle"); + auto sleepStart = Platform::GetTimeSeconds(); Platform::Sleep(1); + auto sleepEnd = Platform::GetTimeSeconds(); + EngineIdleTime += sleepEnd - sleepStart; } } @@ -227,6 +231,7 @@ int32 Engine::Main(const Char* cmdLine) OnUpdate(); OnLateUpdate(); Time::OnEndUpdate(); + EngineIdleTime = 0; } // Start physics simulation diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 678eada1f..5516e6d53 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -251,6 +251,7 @@ public: }; LevelService LevelServiceInstanceService; +extern double EngineIdleTime; CriticalSection Level::ScenesLock; Array Level::Scenes; @@ -863,6 +864,8 @@ void LevelImpl::flushActions() else if (Engine::GetFramesPerSecond() > 0) targetFps = (float)Engine::GetFramesPerSecond(); context.TimeBudget = Level::StreamingFrameBudget / targetFps; + if (EngineIdleTime > 0.001) + context.TimeBudget += (float)(EngineIdleTime * 0.5); // Increase time budget if engine has some idle time for spare #if USE_EDITOR // Throttle up in Editor context.TimeBudget *= Editor::IsPlayMode ? 1.2f : 2.0f; @@ -871,8 +874,9 @@ void LevelImpl::flushActions() // Throttle up in Debug context.TimeBudget *= 1.2f; #endif - if (context.TimeBudget <= ZeroTolerance) - context.TimeBudget = MAX_float; + if (context.TimeBudget <= 0.0f) + context.TimeBudget = MAX_float; // Unlimited if 0 + context.TimeBudget = Math::Max(context.TimeBudget, 0.001f); // Minimum 1ms // Runs actions in order ScopeLock lock(_sceneActionsLocker); From 0fa53f860a3afc2edfc9eae4ff1a1bc080d78054 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Jun 2025 23:35:03 +0200 Subject: [PATCH 47/84] Add `UseLogInRelease` to engine config to disable logging in Release builds --- Source/Engine/Core/Config.h | 4 ++++ Source/Tools/Flax.Build/Build/ProjectTarget.cs | 4 ++++ Source/Tools/Flax.Build/Configuration.cs | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/Source/Engine/Core/Config.h b/Source/Engine/Core/Config.h index 014ebb0c2..810217050 100644 --- a/Source/Engine/Core/Config.h +++ b/Source/Engine/Core/Config.h @@ -30,13 +30,17 @@ #endif // Enable logging service (saving log to file, can be disabled using -nolog command line) +#ifndef LOG_ENABLE #define LOG_ENABLE 1 +#endif // Enable crash reporting service (stack trace and crash dump collecting) #define CRASH_LOG_ENABLE (!BUILD_RELEASE) // Enable/disable assertion +#ifndef ENABLE_ASSERTION #define ENABLE_ASSERTION (!BUILD_RELEASE) +#endif // Enable/disable assertion for Engine low layers #define ENABLE_ASSERTION_LOW_LAYERS ENABLE_ASSERTION && (BUILD_DEBUG || FLAX_TESTS) diff --git a/Source/Tools/Flax.Build/Build/ProjectTarget.cs b/Source/Tools/Flax.Build/Build/ProjectTarget.cs index 77b7cfe94..e408452a0 100644 --- a/Source/Tools/Flax.Build/Build/ProjectTarget.cs +++ b/Source/Tools/Flax.Build/Build/ProjectTarget.cs @@ -79,6 +79,10 @@ namespace Flax.Build options.CompileEnv.PreprocessorDefinitions.Add("USE_LARGE_WORLDS"); options.ScriptingAPI.Defines.Add("USE_LARGE_WORLDS"); } + if (!EngineConfiguration.UseLogInRelease && !IsEditor) + { + options.CompileEnv.PreprocessorDefinitions.Add("LOG_ENABLE=0"); + } // Add include paths for this and all referenced projects sources foreach (var project in Project.GetAllProjects()) diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index f6ad3b3a5..3bb84760e 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -276,6 +276,12 @@ namespace Flax.Build [CommandLine("useDotNet", "1 to enable .NET support in build, 0 to enable Mono support in build")] public static bool UseDotNet = true; + /// + /// True if enable logging in Release game builds. + /// + [CommandLine("useLogInRelease", "Can be used to disable logging in Release game builds")] + public static bool UseLogInRelease = true; + public static bool WithCSharp(NativeCpp.BuildOptions options) { return UseCSharp || options.Target.IsEditor; From 4240646ec78b812b9be49471cbbef87a71425db1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Jun 2025 08:31:32 +0200 Subject: [PATCH 48/84] Update minimum Windows version to `10` (to match .NET 8) --- .../Engine/Platform/Windows/WindowsPlatform.cpp | 16 +++++----------- Source/Engine/Video/MF/VideoBackendMF.cpp | 2 +- .../Platforms/Windows/WindowsToolchain.cs | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 697174a47..272d38cf7 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -556,14 +556,8 @@ void WindowsPlatform::PreInit(void* hInstance) FlaxDbgHelpUnlock(); #endif + // Get system version GetWindowsVersion(WindowsName, VersionMajor, VersionMinor, VersionBuild); - - // Validate platform - if (VersionMajor < 6) - { - Error(TEXT("Not supported operating system version.")); - exit(-1); - } } bool WindowsPlatform::IsWindows10() @@ -640,25 +634,25 @@ bool WindowsPlatform::Init() // Check if can run Engine on current platform #if WINVER >= 0x0A00 - if (!IsWindows10OrGreater() && !IsWindowsServer()) + if (VersionMajor < 10 && !IsWindowsServer()) { Platform::Fatal(TEXT("Flax Engine requires Windows 10 or higher.")); return true; } #elif WINVER >= 0x0603 - if (!IsWindows8Point1OrGreater() && !IsWindowsServer()) + if ((VersionMajor < 8 || (VersionMajor == 8 && VersionMinor == 0)) && !IsWindowsServer()) { Platform::Fatal(TEXT("Flax Engine requires Windows 8.1 or higher.")); return true; } #elif WINVER >= 0x0602 - if (!IsWindows8OrGreater() && !IsWindowsServer()) + if (VersionMajor < 8 && !IsWindowsServer()) { Platform::Fatal(TEXT("Flax Engine requires Windows 8 or higher.")); return true; } #else - if (!IsWindows7OrGreater() && !IsWindowsServer()) + if (VersionMajor < 7 && !IsWindowsServer()) { Platform::Fatal(TEXT("Flax Engine requires Windows 7 or higher.")); return true; diff --git a/Source/Engine/Video/MF/VideoBackendMF.cpp b/Source/Engine/Video/MF/VideoBackendMF.cpp index 01d6ec481..75950f3aa 100644 --- a/Source/Engine/Video/MF/VideoBackendMF.cpp +++ b/Source/Engine/Video/MF/VideoBackendMF.cpp @@ -17,7 +17,7 @@ // Fix compilation for Windows 8.1 on the latest Windows SDK typedef enum _MFVideoSphericalFormat { } MFVideoSphericalFormat; #endif -#if !defined(MF_SOURCE_READER_CURRENT_TYPE_INDEX) && !defined(PLATFORM_GDK) +#if !defined(MF_SOURCE_READER_CURRENT_TYPE_INDEX) && !defined(PLATFORM_GDK) && WINVER < _WIN32_WINNT_WIN10 // Fix compilation for Windows 7 on the latest Windows SDK #define MF_SOURCE_READER_CURRENT_TYPE_INDEX 0xFFFFFFFF #endif diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs index b33ca5e25..428cc2437 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs @@ -14,7 +14,7 @@ namespace Flax.Build /// Specifies the minimum Windows version to use (eg. 10). /// [CommandLine("winMinVer", "", "Specifies the minimum Windows version to use (eg. 10).")] - public static string WindowsMinVer = "7"; + public static string WindowsMinVer = "10"; } } From 7606c9ac1227bbadd8ee3dbce3b999ae799fc0f2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Jun 2025 17:03:19 +0200 Subject: [PATCH 49/84] Update minimum CPU arch requirement on Windows to AVX2 with SSE4.2 94.48% support on PC according to Steam Hardware & Software Survey: May 2025 (https://store.steampowered.com/hwsurvey/) --- Source/Engine/Platform/Defines.h | 18 ++-- .../Platform/Windows/WindowsPlatform.cpp | 101 +++++++++++++++--- .../Build/NativeCpp/CompileEnvironment.cs | 47 ++++++++ Source/Tools/Flax.Build/Build/Target.cs | 19 ++++ Source/Tools/Flax.Build/Build/Toolchain.cs | 5 + .../Platforms/Windows/WindowsToolchain.cs | 27 ++++- 6 files changed, 188 insertions(+), 29 deletions(-) diff --git a/Source/Engine/Platform/Defines.h b/Source/Engine/Platform/Defines.h index b5b3274e2..29f64052d 100644 --- a/Source/Engine/Platform/Defines.h +++ b/Source/Engine/Platform/Defines.h @@ -207,28 +207,22 @@ API_ENUM() enum class ArchitectureType #define PLATFORM_UNIX_FAMILY (PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_APPLE_FAMILY) // SIMD defines -#if defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) || defined(__SSE2__) +#if !defined(PLATFORM_SIMD_SSE2) && (defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) || defined(__SSE2__)) #define PLATFORM_SIMD_SSE2 1 -#if defined(__SSE3__) +#if !defined(PLATFORM_SIMD_SSE3) && (defined(__SSE3__)) #define PLATFORM_SIMD_SSE3 1 #endif -#if defined(__SSE4__) -#define PLATFORM_SIMD_SSE4 1 -#endif -#if defined(__SSE4_1__) +#if !defined(PLATFORM_SIMD_SSE4_1) && (defined(__SSE4_1__)) #define PLATFORM_SIMD_SSE4_1 1 #endif -#if defined(__SSE4_2__) +#if !defined(PLATFORM_SIMD_SSE4_2) && (defined(__SSE4_2__)) #define PLATFORM_SIMD_SSE4_2 1 #endif #endif -#if defined(_M_ARM) || defined(__ARM_NEON__) || defined(__ARM_NEON) +#if !defined(PLATFORM_SIMD_NEON) && (defined(_M_ARM) || defined(__ARM_NEON__) || defined(__ARM_NEON)) #define PLATFORM_SIMD_NEON 1 #endif -#if defined(_M_PPC) || defined(__CELLOS_LV2__) -#define PLATFORM_SIMD_VMX 1 -#endif -#define PLATFORM_SIMD (PLATFORM_SIMD_SSE2 || PLATFORM_SIMD_SSE3 || PLATFORM_SIMD_SSE4 || PLATFORM_SIMD_NEON || PLATFORM_SIMD_VMX) +#define PLATFORM_SIMD (PLATFORM_SIMD_SSE2 || PLATFORM_SIMD_SSE3 || PLATFORM_SIMD_SSE4_1 || PLATFORM_SIMD_SSE4_2 || PLATFORM_SIMD_NEON) // Unicode text macro #if !defined(TEXT) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 272d38cf7..116cb3deb 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -257,6 +257,37 @@ void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionM RegCloseKey(hKey); } +#if PLATFORM_ARCH_X86 || PLATFORM_ARCH_X64 + +struct CPUBrand +{ + char Buffer[0x40]; + + CPUBrand() + { + Buffer[0] = 0; + int32 cpuInfo[4]; + __cpuid(cpuInfo, 0x80000000); + if (cpuInfo[0] >= 0x80000004) + { + // Get name + for (uint32 i = 0; i < 3; i++) + { + __cpuid(cpuInfo, 0x80000002 + i); + memcpy(Buffer + i * sizeof(cpuInfo), cpuInfo, sizeof(cpuInfo)); + } + + // Trim ending whitespaces + int32 size = StringUtils::Length(Buffer); + while (size > 1 && Buffer[size - 1] == ' ') + size--; + Buffer[size] = 0; + } + } +}; + +#endif + LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Find window to process that message @@ -517,6 +548,60 @@ void WindowsPlatform::PreInit(void* hInstance) ASSERT(hInstance); Instance = hInstance; +#if PLATFORM_ARCH_X86 || PLATFORM_ARCH_X64 + // Check the minimum vector instruction set support + int32 cpuInfo[4] = { -1 }; + __cpuid(cpuInfo, 0); + int32 cpuInfoSize = cpuInfo[0]; + __cpuid(cpuInfo, 1); + bool SSE2 = cpuInfo[3] & (1u << 26); + bool SSE3 = cpuInfo[2] & (1u << 0); + bool SSE41 = cpuInfo[2] & (1u << 19); + bool SSE42 = cpuInfo[2] & (1u << 20); + bool AVX = cpuInfo[2] & (1u << 28); + bool POPCNT = cpuInfo[2] & (1u << 23); + bool AVX2 = false; + if (cpuInfoSize >= 7) + { + __cpuid(cpuInfo, 7); + AVX2 = cpuInfo[1] & (1u << 5) && (_xgetbv(0) & 6) == 6; + } + const Char* missingFeature = nullptr; +#if defined(__AVX__) + if (!AVX) + missingFeature = TEXT("AVX"); +#endif +#if defined(__AVX2__) + if (!AVX2) + missingFeature = TEXT("AVX2"); +#endif +#if PLATFORM_SIMD_SSE2 + if (!SSE2) + missingFeature = TEXT("SSE2"); +#endif +#if PLATFORM_SIMD_SSE3 + if (!SSE3) + missingFeature = TEXT("SSE3"); +#endif +#if PLATFORM_SIMD_SSE4_1 + if (!SSE41) + missingFeature = TEXT("SSE4.1"); +#endif +#if PLATFORM_SIMD_SSE4_2 + if (!SSE42) + missingFeature = TEXT("SSE4.2"); + if (!POPCNT) + missingFeature = TEXT("POPCNT"); +#endif + if (missingFeature) + { + // Not supported CPU + CPUBrand cpu; + Error(String::Format(TEXT("Cannot start program due to lack of CPU feature {}.\n\n{}"), missingFeature, String(cpu.Buffer))); + exit(-1); + } +#endif + // Disable the process from being showing "ghosted" while not responding messages during slow tasks DisableProcessWindowsGhosting(); @@ -707,20 +792,8 @@ void WindowsPlatform::LogInfo() #if PLATFORM_ARCH_X86 || PLATFORM_ARCH_X64 // Log CPU brand - { - char brandBuffer[0x40] = {}; - int32 cpuInfo[4] = { -1 }; - __cpuid(cpuInfo, 0x80000000); - if (cpuInfo[0] >= 0x80000004) - { - for (uint32 i = 0; i < 3; i++) - { - __cpuid(cpuInfo, 0x80000002 + i); - memcpy(brandBuffer + i * sizeof(cpuInfo), cpuInfo, sizeof(cpuInfo)); - } - } - LOG(Info, "CPU: {0}", String(brandBuffer)); - } + CPUBrand cpu; + LOG(Info, "CPU: {0}", String(cpu.Buffer)); #endif LOG(Info, "Microsoft {0} {1}-bit ({2}.{3}.{4})", WindowsName, Platform::Is64BitPlatform() ? TEXT("64") : TEXT("32"), VersionMajor, VersionMinor, VersionBuild); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs index 81c620592..7da495b46 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs @@ -84,6 +84,47 @@ namespace Flax.Build.NativeCpp Latest, } + /// + /// The SIMD architecture to use for code generation. + /// + public enum CpuArchitecture + { + /// + /// No specific architecture set. + /// + None, + + /// + /// Intel Advanced Vector Extensions. + /// + AVX, + + /// + /// Enables Intel Advanced Vector Extensions 2. + /// + AVX2, + + /// + /// Intel Advanced Vector Extensions 512. + /// + AVX512, + + /// + /// Intel Streaming SIMD Extensions 2. + /// + SSE2, + + /// + /// Intel Streaming SIMD Extensions 4.2. + /// + SSE4_2, + + /// + /// ARM Neon. + /// + NEON, + } + /// /// The C++ compilation environment required to build source files in the native modules. /// @@ -104,6 +145,11 @@ namespace Flax.Build.NativeCpp /// public Sanitizer Sanitizers = Sanitizer.None; + /// + /// SIMD architecture to use. + /// + public CpuArchitecture CpuArchitecture = CpuArchitecture.None; + /// /// Enables exceptions support. /// @@ -222,6 +268,7 @@ namespace Flax.Build.NativeCpp CppVersion = CppVersion, FavorSizeOrSpeed = FavorSizeOrSpeed, Sanitizers = Sanitizers, + CpuArchitecture = CpuArchitecture, EnableExceptions = EnableExceptions, RuntimeTypeInfo = RuntimeTypeInfo, Inlining = Inlining, diff --git a/Source/Tools/Flax.Build/Build/Target.cs b/Source/Tools/Flax.Build/Build/Target.cs index 4deecb414..fe3a4e075 100644 --- a/Source/Tools/Flax.Build/Build/Target.cs +++ b/Source/Tools/Flax.Build/Build/Target.cs @@ -248,6 +248,25 @@ namespace Flax.Build Modules.Add("Main"); } + switch (options.CompileEnv.CpuArchitecture) + { + case CpuArchitecture.AVX: + case CpuArchitecture.SSE2: + // Basic SEE2 + options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_SIMD_SSE2=1"); break; + case CpuArchitecture.AVX2: + case CpuArchitecture.SSE4_2: + // Assume full support of SEE4.2 and older + options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_SIMD_SSE2=1"); + options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_SIMD_SSE3=1"); + options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_SIMD_SSE4_1=1"); + options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_SIMD_SSE4_2=1"); + break; + case CpuArchitecture.NEON: + options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_SIMD_NEON=1"); + break; + } + options.CompileEnv.EnableExceptions = true; // TODO: try to disable this! options.CompileEnv.Sanitizers = Configuration.Sanitizers; switch (options.Configuration) diff --git a/Source/Tools/Flax.Build/Build/Toolchain.cs b/Source/Tools/Flax.Build/Build/Toolchain.cs index ca05cecc4..d67766cf8 100644 --- a/Source/Tools/Flax.Build/Build/Toolchain.cs +++ b/Source/Tools/Flax.Build/Build/Toolchain.cs @@ -102,6 +102,11 @@ namespace Flax.Build { options.CompileEnv.IncludePaths.AddRange(SystemIncludePaths); options.LinkEnv.LibraryPaths.AddRange(SystemLibraryPaths); + + if (options.Architecture == TargetArchitecture.x64 || options.Architecture == TargetArchitecture.x86) + options.CompileEnv.CpuArchitecture = CpuArchitecture.AVX; + else if (options.Architecture == TargetArchitecture.ARM64 || options.Architecture == TargetArchitecture.ARM) + options.CompileEnv.CpuArchitecture = CpuArchitecture.NEON; } /// diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs index 428cc2437..7537cdecf 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs @@ -15,6 +15,12 @@ namespace Flax.Build /// [CommandLine("winMinVer", "", "Specifies the minimum Windows version to use (eg. 10).")] public static string WindowsMinVer = "10"; + + /// + /// Specifies the minimum CPU architecture type to support (on x86/x64). + /// + [CommandLine("winCpuArch", "", "Specifies the minimum CPU architecture type to support (om x86/x64).")] + public static CpuArchitecture WindowsCpuArch = CpuArchitecture.AVX2; // 94.48% support on PC according to Steam Hardware & Software Survey: May 2025 (https://store.steampowered.com/hwsurvey/) } } @@ -80,6 +86,18 @@ namespace Flax.Build.Platforms options.CompileEnv.PreprocessorDefinitions.Add("USE_SOFT_INTRINSICS"); options.LinkEnv.InputLibraries.Add("softintrin.lib"); } + + options.CompileEnv.CpuArchitecture = Configuration.WindowsCpuArch; + if (_minVersion.Major <= 7 && options.CompileEnv.CpuArchitecture == CpuArchitecture.AVX2) + { + // Old Windows had lower support ratio for latest CPU features + options.CompileEnv.CpuArchitecture = CpuArchitecture.AVX; + } + if (_minVersion.Major >= 11 && options.CompileEnv.CpuArchitecture == CpuArchitecture.AVX) + { + // Windows 11 has hard requirement on SSE4.2 + options.CompileEnv.CpuArchitecture = CpuArchitecture.SSE4_2; + } } /// @@ -87,10 +105,13 @@ namespace Flax.Build.Platforms { base.SetupCompileCppFilesArgs(graph, options, args); - if (Toolset >= WindowsPlatformToolset.v142 && _minVersion.Major >= 11) + switch (options.CompileEnv.CpuArchitecture) { - // Windows 11 requires SSE4.2 - args.Add("/d2archSSE42"); + case CpuArchitecture.AVX: args.Add("/arch:AVX"); break; + case CpuArchitecture.AVX2: args.Add("/arch:AVX2"); break; + case CpuArchitecture.AVX512: args.Add("/arch:AVX512"); break; + case CpuArchitecture.SSE2: args.Add("/arch:SSE2"); break; + case CpuArchitecture.SSE4_2: args.Add("/arch:SSE4.2"); break; } } From bdc87c7bc6582f1be10774cf03664a7f9ee8e724 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Jun 2025 17:26:39 +0200 Subject: [PATCH 50/84] Update min supported version of macOS to 13 and iOS to 15 --- Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp | 2 +- Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs | 2 +- Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp index 69f32f588..1d447027b 100644 --- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp @@ -186,7 +186,7 @@ bool MacPlatformTools::OnPostProcess(CookingData& data) ADD_ENTRY("CFBundlePackageType", "APPL"); ADD_ENTRY("NSPrincipalClass", "NSApplication"); ADD_ENTRY("LSApplicationCategoryType", "public.app-category.games"); - ADD_ENTRY("LSMinimumSystemVersion", "10.15"); + ADD_ENTRY("LSMinimumSystemVersion", "13"); ADD_ENTRY("CFBundleIconFile", "icon.icns"); ADD_ENTRY_STR("CFBundleExecutable", executableName); ADD_ENTRY_STR("CFBundleIdentifier", appIdentifier); diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs index a9210274f..1d5315626 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs @@ -11,7 +11,7 @@ namespace Flax.Build /// Specifies the minimum Mac OSX version to use (eg. 10.14). /// [CommandLine("macOSXMinVer", "", "Specifies the minimum Mac OSX version to use (eg. 10.14).")] - public static string MacOSXMinVer = "10.15"; + public static string MacOSXMinVer = "13"; } } diff --git a/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs b/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs index 58bf44b77..44647a607 100644 --- a/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs @@ -12,7 +12,7 @@ namespace Flax.Build /// Specifies the minimum iOS version to use (eg. 14). /// [CommandLine("iOSMinVer", "", "Specifies the minimum iOS version to use (eg. 14).")] - public static string iOSMinVer = "14"; + public static string iOSMinVer = "15"; } } From eadb4411ffd53290814657bfaa009695f4c45982 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Jun 2025 17:35:02 +0200 Subject: [PATCH 51/84] Fix crash in GPU Memory profiler if resource went null --- Source/Editor/Windows/Profiler/MemoryGPU.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs index ce266777d..74f14b584 100644 --- a/Source/Editor/Windows/Profiler/MemoryGPU.cs +++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs @@ -146,6 +146,8 @@ namespace FlaxEditor.Windows.Profiler { var gpuResource = _gpuResourcesCached[i]; ref var resource = ref resources[i]; + if (!gpuResource) + continue; // Try to reuse cached resource info var gpuResourceId = gpuResource.ID; From e2d9452994a3fc1ec9c8d1ee02fd39a770e1fa67 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Jun 2025 18:05:01 +0200 Subject: [PATCH 52/84] Add unified min Clang version `13` for Linux --- Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs | 6 +++--- Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs | 4 ++-- Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs | 5 ++--- Source/Tools/Flax.Build/Deps/Dependencies/curl.cs | 4 ++-- .../Tools/Flax.Build/Deps/Dependencies/freetype.cs | 4 ++-- Source/Tools/Flax.Build/Deps/Dependencies/mono.cs | 4 ++-- Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs | 4 ++-- .../Flax.Build/Platforms/Linux/LinuxToolchain.cs | 12 ++++++++++++ 8 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs index 50235aa10..f72928bf8 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs @@ -124,9 +124,9 @@ namespace Flax.Deps.Dependencies { var envVars = new Dictionary { - { "CC", "clang-13" }, - { "CC_FOR_BUILD", "clang-13" }, - { "CXX", "clang++-13" }, + { "CC", "clang-" + Configuration.LinuxClangMinVer }, + { "CC_FOR_BUILD", "clang-" + Configuration.LinuxClangMinVer }, + { "CXX", "clang++-" + Configuration.LinuxClangMinVer }, }; // Build for Linux diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs index 6a783d73f..7b42486e2 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs @@ -121,8 +121,8 @@ namespace Flax.Deps.Dependencies }; var envVars = new Dictionary { - { "CC", "clang-7" }, - { "CC_FOR_BUILD", "clang-7" } + { "CC", "clang-" + Configuration.LinuxClangMinVer }, + { "CC_FOR_BUILD", "clang-" + Configuration.LinuxClangMinVer } }; var config = "-DALSOFT_REQUIRE_ALSA=ON -DALSOFT_REQUIRE_OSS=ON -DALSOFT_REQUIRE_PORTAUDIO=ON -DALSOFT_REQUIRE_PULSEAUDIO=ON -DALSOFT_REQUIRE_JACK=ON -DALSOFT_EMBED_HRTF_DATA=YES"; diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs index a82b1f59b..32e2d2f38 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Xml; using Flax.Build; using Flax.Build.Platforms; -using Flax.Build.Projects.VisualStudio; using Flax.Deploy; namespace Flax.Deps.Dependencies @@ -237,8 +236,8 @@ namespace Flax.Deps.Dependencies break; } case TargetPlatform.Linux: - envVars.Add("CC", "clang-7"); - envVars.Add("CC_FOR_BUILD", "clang-7"); + envVars.Add("CC", "clang-" + Configuration.LinuxClangMinVer); + envVars.Add("CC_FOR_BUILD", "clang-" + Configuration.LinuxClangMinVer); break; case TargetPlatform.Mac: break; default: throw new InvalidPlatformException(BuildPlatform); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs b/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs index 4eaf8df4b..a474b9566 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs @@ -105,8 +105,8 @@ namespace Flax.Deps.Dependencies }; var envVars = new Dictionary { - { "CC", "clang-7" }, - { "CC_FOR_BUILD", "clang-7" }, + { "CC", "clang-" + Configuration.LinuxClangMinVer }, + { "CC_FOR_BUILD", "clang-" + Configuration.LinuxClangMinVer }, }; var buildDir = Path.Combine(root, "build"); SetupDirectory(buildDir, true); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs b/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs index 6d104d563..ac0079401 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs @@ -116,8 +116,8 @@ namespace Flax.Deps.Dependencies { var envVars = new Dictionary { - { "CC", "clang-7" }, - { "CC_FOR_BUILD", "clang-7" } + { "CC", "clang-" + Configuration.LinuxClangMinVer }, + { "CC_FOR_BUILD", "clang-" + Configuration.LinuxClangMinVer } }; // Fix scripts diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs b/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs index dd52114c2..ddf1cc15d 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs @@ -546,8 +546,8 @@ namespace Flax.Deps.Dependencies { var envVars = new Dictionary { - { "CC", "clang-7" }, - { "CXX", "clang++-7" } + { "CC", "clang-" + Configuration.LinuxClangMinVer }, + { "CXX", "clang++-" + Configuration.LinuxClangMinVer } }; var monoOptions = new[] { diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs b/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs index d841e281c..195c0d8cb 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs @@ -365,8 +365,8 @@ namespace Flax.Deps.Dependencies var envVars = new Dictionary { - { "CC", "clang-7" }, - { "CC_FOR_BUILD", "clang-7" } + { "CC", "clang-" + Configuration.LinuxClangMinVer }, + { "CC_FOR_BUILD", "clang-" + Configuration.LinuxClangMinVer } }; var buildDir = Path.Combine(root, "build"); diff --git a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs index ebbdc5b46..f4e7f06cf 100644 --- a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs @@ -5,6 +5,18 @@ using System.IO; using Flax.Build.Graph; using Flax.Build.NativeCpp; +namespace Flax.Build +{ + partial class Configuration + { + /// + /// Specifies the minimum Clang compiler version to use on Linux (eg. 10). + /// + [CommandLine("linuxClangMinVer", "", "Specifies the minimum Clang compiler version to use on Linux (eg. 10).")] + public static string LinuxClangMinVer = "13"; + } +} + namespace Flax.Build.Platforms { /// From 766091045b1be027321b8cc33f74d50254ffe6c1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Jun 2025 18:21:12 +0200 Subject: [PATCH 53/84] Improve version parsing to share code --- .../Flax.Build/Platforms/Linux/LinuxToolchain.cs | 9 +++++++-- .../Flax.Build/Platforms/Windows/WindowsToolchain.cs | 9 ++------- Source/Tools/Flax.Build/Utilities/Utilities.cs | 12 ++++++++++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs index f4e7f06cf..174b2bcee 100644 --- a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs @@ -1,9 +1,10 @@ // Copyright (c) Wojciech Figat. All rights reserved. -using System.Collections.Generic; -using System.IO; using Flax.Build.Graph; using Flax.Build.NativeCpp; +using System; +using System.Collections.Generic; +using System.IO; namespace Flax.Build { @@ -34,6 +35,10 @@ namespace Flax.Build.Platforms public LinuxToolchain(LinuxPlatform platform, TargetArchitecture architecture) : base(platform, architecture, platform.ToolchainRoot, platform.Compiler) { + // Check version + if (Utilities.ParseVersion(Configuration.LinuxClangMinVer, out var minClangVer) && ClangVersion < minClangVer) + Log.Error($"Old Clang version {ClangVersion}. Minimum supported is {minClangVer}."); + // Setup system paths var includePath = Path.Combine(ToolsetRoot, "usr", "include"); if (Directory.Exists(includePath)) diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs index 7537cdecf..5154cdbf9 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs @@ -44,13 +44,8 @@ namespace Flax.Build.Platforms : base(platform, architecture, WindowsPlatformToolset.Latest, WindowsPlatformSDK.Latest) { // Select minimum Windows version - if (!Version.TryParse(Configuration.WindowsMinVer, out _minVersion)) - { - if (int.TryParse(Configuration.WindowsMinVer, out var winMinVerMajor)) - _minVersion = new Version(winMinVerMajor, 0); - else - _minVersion = new Version(7, 0); - } + if (!Utilities.ParseVersion(Configuration.WindowsMinVer, out _minVersion)) + _minVersion = new Version(7, 0); } /// diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 2b971f153..85f791d09 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -818,5 +818,17 @@ namespace Flax.Build return 0; }); } + + internal static bool ParseVersion(string text, out Version ver) + { + if (Version.TryParse(text, out ver)) + return true; + if (int.TryParse(text, out var major)) + { + ver = new Version(major, 0); + return true; + } + return false; + } } } From bd2add7edd4b7ecf9ea5e1cce6987ce950ce07a2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Jun 2025 23:15:58 +0200 Subject: [PATCH 54/84] Tweak memory command tip --- Source/Editor/Windows/Profiler/Memory.cs | 2 +- Source/Engine/Profiler/ProfilerMemory.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index 6958b828b..a74472a8c 100644 --- a/Source/Editor/Windows/Profiler/Memory.cs +++ b/Source/Editor/Windows/Profiler/Memory.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.Windows.Profiler { _warningText = new Label { - Text = "Detailed memory profiling is disabled. Run with command line: -mem", + Text = "Detailed memory profiling is disabled. Run with command line '-mem'", TextColor = Color.Red, Visible = false, Parent = layout, diff --git a/Source/Engine/Profiler/ProfilerMemory.cpp b/Source/Engine/Profiler/ProfilerMemory.cpp index d53e48b17..c936ff5b2 100644 --- a/Source/Engine/Profiler/ProfilerMemory.cpp +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -160,7 +160,7 @@ namespace // Warn that data might be missing due to inactive profiler if (!ProfilerMemory::Enabled) - output.AppendLine(TEXT("Detailed memory profiling is disabled. Run with command line: -mem")); + output.AppendLine(TEXT("Detailed memory profiling is disabled. Run with command line '-mem'")); } #ifdef USE_TRACY_MEMORY_PLOTS From 62e329ac6e491e8cb0eea6acea81de6eef4c6d3b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 18 Jun 2025 23:00:43 +0200 Subject: [PATCH 55/84] Add more memory tags for Navigation --- Source/Engine/Navigation/NavMesh.cpp | 1 + Source/Engine/Navigation/NavMeshBuilder.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index ee3e48f3e..b48bf26c7 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -110,6 +110,7 @@ void NavMesh::OnAssetLoaded(Asset* asset, void* caller) if (Data.Tiles.HasItems()) return; ScopeLock lock(DataAsset->Locker); + PROFILE_MEM(Navigation); // Remove added tiles if (_navMeshActive) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index dbfecefcb..e5fdec5da 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -732,6 +732,7 @@ public: bool Run() override { PROFILE_CPU_NAMED("BuildNavMeshTile"); + PROFILE_MEM(Navigation); const auto navMesh = NavMesh.Get(); if (!navMesh) return false; @@ -1154,6 +1155,7 @@ void ClearNavigation(Scene* scene) void NavMeshBuilder::Update() { + PROFILE_MEM(Navigation); ScopeLock lock(NavBuildQueueLocker); // Process nav mesh building requests and kick the tasks @@ -1204,7 +1206,7 @@ void NavMeshBuilder::Build(Scene* scene, float timeoutMs) } PROFILE_CPU_NAMED("NavMeshBuilder"); - + PROFILE_MEM(Navigation); ScopeLock lock(NavBuildQueueLocker); BuildRequest req; @@ -1241,7 +1243,7 @@ void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float t } PROFILE_CPU_NAMED("NavMeshBuilder"); - + PROFILE_MEM(Navigation); ScopeLock lock(NavBuildQueueLocker); BuildRequest req; From edb68849426fbd527392bdc1c6f74876a306972a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Jun 2025 08:24:26 +0200 Subject: [PATCH 56/84] Optimize PhysX work dispatcher to be shared by all scenes --- .../Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 768788f90..b15b22a9b 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -82,7 +82,6 @@ struct ActionDataPhysX struct ScenePhysX { PxScene* Scene = nullptr; - PxCpuDispatcher* CpuDispatcher = nullptr; PxControllerManager* ControllerManager = nullptr; void* ScratchMemory = nullptr; Vector3 Origin = Vector3::Zero; @@ -542,6 +541,7 @@ namespace { PxFoundation* Foundation = nullptr; PxPhysics* PhysX = nullptr; + PxDefaultCpuDispatcher* CpuDispatcher = nullptr; #if WITH_PVD PxPvd* PVD = nullptr; #endif @@ -1734,6 +1734,7 @@ void PhysicsBackend::Shutdown() #if WITH_PVD RELEASE_PHYSX(PVD); #endif + RELEASE_PHYSX(CpuDispatcher); RELEASE_PHYSX(Foundation); SceneOrigins.Clear(); } @@ -1791,9 +1792,13 @@ void* PhysicsBackend::CreateScene(const PhysicsSettings& settings) } if (sceneDesc.cpuDispatcher == nullptr) { - scenePhysX->CpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 1, 4)); - CHECK_INIT(scenePhysX->CpuDispatcher, "PxDefaultCpuDispatcherCreate failed!"); - sceneDesc.cpuDispatcher = scenePhysX->CpuDispatcher; + if (CpuDispatcher == nullptr) + { + uint32 threads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 1, 4); + CpuDispatcher = PxDefaultCpuDispatcherCreate(threads); + CHECK_INIT(CpuDispatcher, "PxDefaultCpuDispatcherCreate failed!"); + } + sceneDesc.cpuDispatcher = CpuDispatcher; } switch (settings.BroadPhaseType) { @@ -1855,7 +1860,6 @@ void PhysicsBackend::DestroyScene(void* scene) } #endif RELEASE_PHYSX(scenePhysX->ControllerManager); - SAFE_DELETE(scenePhysX->CpuDispatcher); Allocator::Free(scenePhysX->ScratchMemory); scenePhysX->Scene->release(); From 6144f6c74e16961992cb8599fb62b29772fa8002 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Jun 2025 09:50:07 +0200 Subject: [PATCH 57/84] Optimize physics simulation with higher limit of `8` threads --- Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index b15b22a9b..673b1e96d 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -1794,7 +1794,7 @@ void* PhysicsBackend::CreateScene(const PhysicsSettings& settings) { if (CpuDispatcher == nullptr) { - uint32 threads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 1, 4); + uint32 threads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 1, 8); CpuDispatcher = PxDefaultCpuDispatcherCreate(threads); CHECK_INIT(CpuDispatcher, "PxDefaultCpuDispatcherCreate failed!"); } From 4ac870f7012964f699daa94b2f66e075ab371d8c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Jun 2025 13:57:50 +0200 Subject: [PATCH 58/84] Optimize physics transformation updates propagation in async via Job System --- Source/Engine/Physics/Actors/RigidBody.cpp | 8 +++++ .../Physics/PhysX/PhysicsBackendPhysX.cpp | 31 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index a58911dfb..565bd9fbd 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -468,6 +468,14 @@ void RigidBody::OnActiveTransformChanged() void RigidBody::BeginPlay(SceneBeginData* data) { +#if USE_EDITOR || !BUILD_RELEASE + // FlushActiveTransforms runs in async for each separate actor thus we don't support two rigidbodies that transformations depend on each other + if (Cast(GetParent())) + { + LOG(Warning, "Rigid Body '{0}' is attached to other Rigid Body which is not unsupported and might cause physical simulation instability.", GetNamePath()); + } +#endif + // Create rigid body ASSERT(_actor == nullptr); void* scene = GetPhysicsScene()->GetPhysicsScene(); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 673b1e96d..99b54e8fc 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -1901,6 +1901,23 @@ void PhysicsBackend::StartSimulateScene(void* scene, float dt) scenePhysX->Stepper.renderDone(); } +PxActor** CachedActiveActors; +int64 CachedActiveActorsCount; +volatile int64 CachedActiveActorIndex; + +void FlushActiveTransforms(int32 i) +{ + PROFILE_CPU(); + int64 index; + while ((index = Platform::InterlockedIncrement(&CachedActiveActorIndex)) < CachedActiveActorsCount) + { + const auto pxActor = (PxRigidActor*)CachedActiveActors[index]; + auto actor = static_cast(pxActor->userData); + if (actor) + actor->OnActiveTransformChanged(); + } +} + void PhysicsBackend::EndSimulateScene(void* scene) { PROFILE_MEM(Physics); @@ -1919,10 +1936,18 @@ void PhysicsBackend::EndSimulateScene(void* scene) // Gather change info PxU32 activeActorsCount; PxActor** activeActors = scenePhysX->Scene->getActiveActors(activeActorsCount); - if (activeActorsCount > 0) + + // Update changed transformations + if (activeActorsCount > 50 && JobSystem::GetThreadsCount() > 1) + { + // Run in async via job system + CachedActiveActors = activeActors; + CachedActiveActorsCount = activeActorsCount; + CachedActiveActorIndex = -1; + JobSystem::Execute(FlushActiveTransforms, JobSystem::GetThreadsCount()); + } + else { - // Update changed transformations - // TODO: use jobs system if amount if huge for (uint32 i = 0; i < activeActorsCount; i++) { const auto pxActor = (PxRigidActor*)*activeActors++; From 2e10d776e9bc4e80323a3bb82f62337e21b36971 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Jun 2025 14:04:06 +0200 Subject: [PATCH 59/84] Optimize updating actor rendering entry with better thread locking that support async writes on actor update --- Source/Engine/Level/Scene/SceneRendering.cpp | 14 +++++++------- Source/Engine/Level/Scene/SceneRendering.h | 4 ++-- Source/Engine/Threading/ConcurrentSystemLocker.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index fe2bc310f..4a88703ae 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -43,7 +43,7 @@ FORCE_INLINE bool FrustumsListCull(const BoundingSphere& bounds, const ArrayScenes.Add(this); // Add additional lock during scene rendering (prevents any Actors cache modifications on content streaming threads - eg. when model residency changes) - Locker.Lock(); + Locker.Begin(false); } else if (category == PostRender) { // Release additional lock - Locker.Unlock(); + Locker.End(false); } auto& view = renderContextBatch.GetMainContext().View; auto& list = Actors[(int32)category]; @@ -127,7 +127,7 @@ void SceneRendering::CollectPostFxVolumes(RenderContext& renderContext) void SceneRendering::Clear() { - ScopeLock lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker); for (auto* listener : _listeners) { listener->OnSceneRenderingClear(this); @@ -149,7 +149,7 @@ void SceneRendering::AddActor(Actor* a, int32& key) return; PROFILE_MEM(Graphics); const int32 category = a->_drawCategory; - ScopeLock lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker); auto& list = Actors[category]; if (FreeActors[category].HasItems()) { @@ -174,7 +174,7 @@ void SceneRendering::AddActor(Actor* a, int32& key) void SceneRendering::UpdateActor(Actor* a, int32& key, ISceneRenderingListener::UpdateFlags flags) { const int32 category = a->_drawCategory; - ScopeLock lock(Locker); + ConcurrentSystemLocker::ReadScope lock(Locker); // Read-access only as list doesn't get resized (like Add/Remove do) so allow updating actors from different threads at once auto& list = Actors[category]; if (list.Count() <= key) // Ignore invalid key softly return; @@ -193,7 +193,7 @@ void SceneRendering::UpdateActor(Actor* a, int32& key, ISceneRenderingListener:: void SceneRendering::RemoveActor(Actor* a, int32& key) { const int32 category = a->_drawCategory; - ScopeLock lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker); auto& list = Actors[category]; if (list.Count() > key) // Ignore invalid key softly (eg. list after batch clear during scene unload) { diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index b24dcdfa9..59f997f6b 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -7,7 +7,7 @@ #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Math/BoundingFrustum.h" #include "Engine/Level/Actor.h" -#include "Engine/Platform/CriticalSection.h" +#include "Engine/Threading/ConcurrentSystemLocker.h" class SceneRenderTask; class SceneRendering; @@ -102,7 +102,7 @@ public: Array Actors[MAX]; Array FreeActors[MAX]; Array PostFxProviders; - CriticalSection Locker; + ConcurrentSystemLocker Locker; private: #if USE_EDITOR diff --git a/Source/Engine/Threading/ConcurrentSystemLocker.cpp b/Source/Engine/Threading/ConcurrentSystemLocker.cpp index c8569b119..f8eab96d9 100644 --- a/Source/Engine/Threading/ConcurrentSystemLocker.cpp +++ b/Source/Engine/Threading/ConcurrentSystemLocker.cpp @@ -18,7 +18,7 @@ RETRY: { // Someone else is doing opposite operation so wait for it's end // TODO: use ConditionVariable+CriticalSection to prevent active-waiting - Platform::Sleep(1); + Platform::Sleep(0); goto RETRY; } From d3a50cdacb9349487fde4ba9951bc214f85f5fba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Jun 2025 09:05:25 +0200 Subject: [PATCH 60/84] Optimize `Actor::DestroyChildren` --- Source/Editor/Modules/SceneEditingModule.cs | 4 ++ Source/Editor/Modules/SceneModule.cs | 45 +++++++++++++++ Source/Editor/SceneGraph/SceneGraphNode.cs | 2 +- Source/Engine/Level/Actor.cpp | 63 ++++++++++++++++++++- Source/Engine/Level/Level.cpp | 8 +++ Source/Engine/Level/Level.h | 6 ++ 6 files changed, 124 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 3c7130615..11ab2fcb3 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -711,7 +711,11 @@ namespace FlaxEditor.Modules private void OnActorChildNodesDispose(ActorNode node) { + if (Selection.Count == 0) + return; + // TODO: cache if selection contains any actor child node and skip this loop if no need to iterate + // TODO: or build a hash set with selected nodes for quick O(1) checks (cached until selection changes) // Deselect child nodes for (int i = 0; i < node.ChildNodes.Count; i++) diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 56c420964..9ca92ddce 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; @@ -658,6 +659,48 @@ namespace FlaxEditor.Modules //node?.TreeNode.OnActiveChanged(); } + private void OnActorDestroyChildren(Actor actor) + { + // Instead of doing OnActorParentChanged for every child lets remove all of them at once from that actor + ActorNode node = GetActorNode(actor); + if (node != null) + { + if (Editor.SceneEditing.HasSthSelected) + { + // Clear selection if one of the removed actors is selected + var selection = new HashSet(); + foreach (var e in Editor.SceneEditing.Selection) + { + if (e is ActorNode q && q.Actor) + selection.Add(q.Actor); + } + var count = actor.ChildrenCount; + for (int i = 0; i < count; i++) + { + var child = actor.GetChild(i); + if (selection.Contains(child)) + { + Editor.SceneEditing.Deselect(); + break; + } + } + } + + // Remove all child nodes (upfront remove all nodes to run faster) + for (int i = 0; i < node.ChildNodes.Count; i++) + { + if (node.ChildNodes[i] is ActorNode child) + child.parentNode = null; + } + node.TreeNode.DisposeChildren(); + for (int i = 0; i < node.ChildNodes.Count; i++) + { + node.ChildNodes[i].Dispose(); + } + node.ChildNodes.Clear(); + } + } + /// /// Gets the actor node. /// @@ -709,6 +752,7 @@ namespace FlaxEditor.Modules Level.ActorOrderInParentChanged += OnActorOrderInParentChanged; Level.ActorNameChanged += OnActorNameChanged; Level.ActorActiveChanged += OnActorActiveChanged; + Level.ActorDestroyChildren += OnActorDestroyChildren; } /// @@ -726,6 +770,7 @@ namespace FlaxEditor.Modules Level.ActorOrderInParentChanged -= OnActorOrderInParentChanged; Level.ActorNameChanged -= OnActorNameChanged; Level.ActorActiveChanged -= OnActorActiveChanged; + Level.ActorDestroyChildren -= OnActorDestroyChildren; // Cleanup graph Root.Dispose(); diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index b6cbdb135..20ac3a6a5 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -27,7 +27,7 @@ namespace FlaxEditor.SceneGraph /// /// The parent node. /// - protected SceneGraphNode parentNode; + internal SceneGraphNode parentNode; /// /// Gets the children list. diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 02210d910..24ca6b139 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -466,12 +466,71 @@ Array Actor::GetChildren(const MClass* type) const void Actor::DestroyChildren(float timeLeft) { + if (Children.IsEmpty()) + return; PROFILE_CPU(); + + // Actors system doesn't support editing scene hierarchy from multiple threads + if (!IsInMainThread() && IsDuringPlay()) + { + LOG(Error, "Editing scene hierarchy is only allowed on a main thread."); + return; + } + + // Get all actors Array children = Children; + + // Inform Editor beforehand + Level::callActorEvent(Level::ActorEventType::OnActorDestroyChildren, this, nullptr); + + if (_scene && IsActiveInHierarchy()) + { + // Disable children + for (Actor* child : children) + { + if (child->IsActiveInHierarchy()) + { + child->OnDisableInHierarchy(); + } + } + } + + Level::ScenesLock.Lock(); + + // Remove children all at once + Children.Clear(); + _isHierarchyDirty = true; + + // Unlink children from scene hierarchy + for (Actor* child : children) + { + child->_parent = nullptr; + if (!_isActiveInHierarchy) + child->_isActive = false; // Force keep children deactivated to reduce overhead during destruction + if (_scene) + child->SetSceneInHierarchy(nullptr); + } + + Level::ScenesLock.Unlock(); + + // Inform actors about this + for (Actor* child : children) + { + child->OnParentChanged(); + } + + // Unlink children for hierarchy + for (Actor* child : children) + { + //child->EndPlay(); + + //child->SetParent(nullptr, false, false); + } + + // Delete objects const bool useGameTime = timeLeft > ZeroTolerance; for (Actor* child : children) { - child->SetParent(nullptr, false, false); child->DeleteObject(timeLeft, useGameTime); } } @@ -1280,7 +1339,6 @@ void Actor::OnActiveChanged() if (wasActiveInTree != IsActiveInHierarchy()) OnActiveInTreeChanged(); - //if (GetScene()) Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, this, nullptr); } @@ -1311,7 +1369,6 @@ void Actor::OnActiveInTreeChanged() void Actor::OnOrderInParentChanged() { - //if (GetScene()) Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, this, nullptr); } diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 5516e6d53..49bef81c3 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -263,6 +263,9 @@ Delegate Level::ActorParentChanged; Delegate Level::ActorOrderInParentChanged; Delegate Level::ActorNameChanged; Delegate Level::ActorActiveChanged; +#if USE_EDITOR +Delegate Level::ActorDestroyChildren; +#endif Delegate Level::SceneSaving; Delegate Level::SceneSaved; Delegate Level::SceneSaveError; @@ -851,6 +854,11 @@ void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) case ActorEventType::OnActorActiveChanged: ActorActiveChanged(a); break; +#if USE_EDITOR + case ActorEventType::OnActorDestroyChildren: + ActorDestroyChildren(a); + break; +#endif } } diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 597bc0a87..484ba35b8 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -549,7 +549,13 @@ private: OnActorOrderInParentChanged = 3, OnActorNameChanged = 4, OnActorActiveChanged = 5, +#if USE_EDITOR + OnActorDestroyChildren = 6, +#endif }; static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b); +#if USE_EDITOR + API_EVENT(Internal) static Delegate ActorDestroyChildren; +#endif }; From d7df403e5e5237f125123fe79b7737c4c63e45e2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Jun 2025 09:05:41 +0200 Subject: [PATCH 61/84] Optimize `ContainerControl.DisposeChildren` --- Source/Engine/UI/GUI/ContainerControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 32b03af0d..017b8ee5c 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -173,7 +173,7 @@ namespace FlaxEngine.GUI // Delete children while (_children.Count > 0) { - _children[0].Dispose(); + _children[^1].Dispose(); } _isLayoutLocked = wasLayoutLocked; From ef5d45874a96452fe6ea8350cf0e41f4290da73c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 22 Jun 2025 12:12:42 +0200 Subject: [PATCH 62/84] Fix compilation regression --- Source/Engine/Level/Actor.cpp | 2 ++ Source/Engine/Navigation/NavMesh.cpp | 1 + Source/Engine/Navigation/NavMeshBuilder.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 24ca6b139..551f6fba9 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -480,8 +480,10 @@ void Actor::DestroyChildren(float timeLeft) // Get all actors Array children = Children; +#if USE_EDITOR // Inform Editor beforehand Level::callActorEvent(Level::ActorEventType::OnActorDestroyChildren, this, nullptr); +#endif if (_scene && IsActiveInHierarchy()) { diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index b48bf26c7..5593d732a 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -5,6 +5,7 @@ #include "Engine/Level/Scene/Scene.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Threading/Threading.h" +#include "Engine/Profiler/ProfilerMemory.h" #if COMPILE_WITH_ASSETS_IMPORTER #include "Engine/Core/Log.h" #include "Engine/ContentImporters/AssetsImportingManager.h" diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index e5fdec5da..e92173846 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -23,6 +23,7 @@ #include "Engine/Terrain/TerrainPatch.h" #include "Engine/Terrain/Terrain.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Level.h" #include From 674fda7375f211655ea81b6644fea082f85ed3c9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Jun 2025 19:50:04 +0200 Subject: [PATCH 63/84] Add resizing to Custom Code nodes in Materials --- Source/Editor/Surface/Archetypes/Material.cs | 150 ++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index e46b1c6fb..bd084e286 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -1,11 +1,13 @@ // Copyright (c) Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEditor.Content.Settings; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Windows.Assets; using FlaxEngine; +using FlaxEngine.GUI; namespace FlaxEditor.Surface.Archetypes { @@ -260,6 +262,148 @@ namespace FlaxEditor.Surface.Archetypes } } + internal sealed class CustomCodeNode : SurfaceNode + { + private Rectangle _resizeButtonRect; + private Float2 _startResizingSize; + private Float2 _startResizingCornerOffset; + private bool _isResizing; + + private int SizeValueIndex => Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array + + private Float2 SizeValue + { + get => (Float2)Values[SizeValueIndex]; + set => SetValue(SizeValueIndex, value, false); + } + + public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + } + + public override bool CanSelect(ref Float2 location) + { + return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location); + } + + public override void OnSurfaceLoaded(SurfaceNodeActions action) + { + base.OnSurfaceLoaded(action); + + var textBox = (TextBox)Children.First(x => x is TextBox); + textBox.AnchorMax = Float2.One; + + var size = SizeValue; + if (Surface != null && Surface.GridSnappingEnabled) + size = Surface.SnapToGrid(size, true); + Resize(size.X, size.Y); + } + + public override void OnValuesChanged() + { + base.OnValuesChanged(); + + var size = SizeValue; + Resize(size.X, size.Y); + } + + protected override void UpdateRectangles() + { + base.UpdateRectangles(); + + const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin; + const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize; + _resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize); + } + + public override void Draw() + { + base.Draw(); + + var style = Style.Current; + if (_isResizing) + { + Render2D.FillRectangle(_resizeButtonRect, style.Selection); + Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder); + } + Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); + } + + public override void OnLostFocus() + { + if (_isResizing) + EndResizing(); + + base.OnLostFocus(); + } + + public override void OnEndMouseCapture() + { + if (_isResizing) + EndResizing(); + + base.OnEndMouseCapture(); + } + + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (base.OnMouseDown(location, button)) + return true; + + if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit) + { + // Start sliding + _isResizing = true; + _startResizingSize = Size; + _startResizingCornerOffset = Size - location; + StartMouseCapture(); + Cursor = CursorType.SizeNWSE; + return true; + } + + return false; + } + + public override void OnMouseMove(Float2 location) + { + if (_isResizing) + { + var emptySize = CalculateNodeSize(0, 0); + var size = Float2.Max(location - emptySize + _startResizingCornerOffset, new Float2(240, 160)); + Resize(size.X, size.Y); + } + else + { + base.OnMouseMove(location); + } + } + + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (button == MouseButton.Left && _isResizing) + { + EndResizing(); + return true; + } + + return base.OnMouseUp(location, button); + } + + private void EndResizing() + { + Cursor = CursorType.Default; + EndMouseCapture(); + _isResizing = false; + if (_startResizingSize != Size) + { + var emptySize = CalculateNodeSize(0, 0); + SizeValue = Size - emptySize; + Surface.MarkAsEdited(false); + } + } + } + internal enum MaterialTemplateInputsMapping { /// @@ -410,13 +554,15 @@ namespace FlaxEditor.Surface.Archetypes new NodeArchetype { TypeID = 8, + Create = (id, context, arch, groupArch) => new CustomCodeNode(id, context, arch, groupArch), Title = "Custom Code", Description = "Custom HLSL shader code expression", Flags = NodeFlags.MaterialGraph, Size = new Float2(300, 200), DefaultValues = new object[] { - "// Here you can add HLSL code\nOutput0 = Input0;" + "// Here you can add HLSL code\nOutput0 = Input0;", + new Float2(300, 200), }, Elements = new[] { @@ -874,6 +1020,7 @@ namespace FlaxEditor.Surface.Archetypes new NodeArchetype { TypeID = 38, + Create = (id, context, arch, groupArch) => new CustomCodeNode(id, context, arch, groupArch), Title = "Custom Global Code", Description = "Custom global HLSL shader code expression (placed before material shader code). Can contain includes to shader utilities or declare functions to reuse later.", Flags = NodeFlags.MaterialGraph, @@ -883,6 +1030,7 @@ namespace FlaxEditor.Surface.Archetypes "// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}", true, (int)MaterialTemplateInputsMapping.Utilities, + new Float2(300, 240), }, Elements = new[] { From 5c37584eca692cb113896c4d3cd6a7d994978226 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Jun 2025 19:50:42 +0200 Subject: [PATCH 64/84] Minor adjustment for alignment of perf-critical variables in rendering --- Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp | 2 +- Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 77cec978d..22780e3ec 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -267,7 +267,7 @@ void GPUContextDX11::SetRenderTarget(GPUTextureView* depthBuffer, const Span(depthBuffer); ID3D11DepthStencilView* dsv = depthBufferDX11 ? depthBufferDX11->DSV() : nullptr; - ID3D11RenderTargetView* rtvs[GPU_MAX_RT_BINDED]; + __declspec(align(16)) ID3D11RenderTargetView* rtvs[GPU_MAX_RT_BINDED]; for (int32 i = 0; i < rts.Length(); i++) { auto rtDX11 = reinterpret_cast(rts[i]); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 5f278a8ae..9cb285ac1 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -977,7 +977,7 @@ void GPUContextDX12::BindVB(const Span& vertexBuffers, const uint32* { ASSERT(vertexBuffers.Length() >= 0 && vertexBuffers.Length() <= GPU_MAX_VB_BINDED); bool vbEdited = _vbCount != vertexBuffers.Length(); - D3D12_VERTEX_BUFFER_VIEW views[GPU_MAX_VB_BINDED]; + __declspec(align(16)) D3D12_VERTEX_BUFFER_VIEW views[GPU_MAX_VB_BINDED]; for (int32 i = 0; i < vertexBuffers.Length(); i++) { const auto vbDX12 = static_cast(vertexBuffers[i]); From 45e82d21f4e15234ce8b7cf39d005fa929f535c8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Jun 2025 19:51:06 +0200 Subject: [PATCH 65/84] Fix `ConcurrentSystemLocker` to guard for a single writer at once --- Source/Engine/Threading/ConcurrentSystemLocker.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Engine/Threading/ConcurrentSystemLocker.cpp b/Source/Engine/Threading/ConcurrentSystemLocker.cpp index f8eab96d9..c8debb561 100644 --- a/Source/Engine/Threading/ConcurrentSystemLocker.cpp +++ b/Source/Engine/Threading/ConcurrentSystemLocker.cpp @@ -22,6 +22,14 @@ RETRY: goto RETRY; } + // Writers have to check themselves to (one write at the same time - just like a mutex) + if (write && Platform::AtomicRead(thisCounter) != 0) + { + // Someone else is doing opposite operation so wait for it's end + Platform::Sleep(0); + goto RETRY; + } + // Mark that we entered this section Platform::InterlockedIncrement(thisCounter); From 1b40775d628202e31dff559a8917e1e44b1f4332 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 27 Jun 2025 11:56:09 +0200 Subject: [PATCH 66/84] Fix deadloop in `HtmlParser` when parsing text with incorrect tags --- Source/Engine/Utilities/HtmlParser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Utilities/HtmlParser.cs b/Source/Engine/Utilities/HtmlParser.cs index 37b614b80..175b0247a 100644 --- a/Source/Engine/Utilities/HtmlParser.cs +++ b/Source/Engine/Utilities/HtmlParser.cs @@ -177,6 +177,8 @@ namespace FlaxEngine.Utilities // Get name of this tag int start = _pos; string s = ParseTagName(); + if (s == string.Empty) + return false; // Special handling bool doctype = _scriptBegin = false; From 8cdec15fa64c365260660039373080762dbefe0d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 27 Jun 2025 15:41:48 +0200 Subject: [PATCH 67/84] Fix `GlobalSignDistanceFieldCustomBuffer` to be thread-safe (scene rendering events are not guarded via mutex anymore) --- Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index a46568f97..56dd196a5 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -197,6 +197,7 @@ public: GPUTexture* Texture = nullptr; GPUTexture* TextureMip = nullptr; Vector3 Origin = Vector3::Zero; + ConcurrentSystemLocker Locker; Array> Cascades; HashSet ObjectTypes; HashSet SDFTextures; @@ -395,6 +396,7 @@ public: { if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { + ConcurrentSystemLocker::WriteScope lock(Locker); OnSceneRenderingDirty(a->GetBox()); } } @@ -403,6 +405,7 @@ public: { if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { + ConcurrentSystemLocker::WriteScope lock(Locker); OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); OnSceneRenderingDirty(a->GetBox()); } @@ -412,6 +415,7 @@ public: { if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { + ConcurrentSystemLocker::WriteScope lock(Locker); OnSceneRenderingDirty(a->GetBox()); } } From 185151b0250bb708c3d67e183b05ee9a542e2c82 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 27 Jun 2025 18:52:25 +0200 Subject: [PATCH 68/84] Minor fixes --- Source/Editor/Surface/Archetypes/Material.cs | 71 ++++++++++++++++++-- Source/Engine/Core/ObjectsRemovalService.cpp | 2 +- Source/Engine/UI/GUI/Common/RichTextBox.cs | 2 +- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index bd084e286..c9066eaa5 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -1,7 +1,6 @@ // Copyright (c) Wojciech Figat. All rights reserved. using System; -using System.Linq; using FlaxEditor.Content.Settings; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; @@ -262,12 +261,55 @@ namespace FlaxEditor.Surface.Archetypes } } +#if false // TODO: finish code editor based on RichTextBoxBase with text block parsing for custom styling + internal sealed class CustomCodeTextBox : RichTextBoxBase + { + protected override void OnParseTextBlocks() + { + base.OnParseTextBlocks(); + + // Single block for a whole text + // TODO: implement code parsing with HLSL syntax + var font = Style.Current.FontMedium; + var style = new TextBlockStyle + { + Font = new FontReference(font), + Color = Style.Current.Foreground, + BackgroundSelectedBrush = new SolidColorBrush(Style.Current.BackgroundSelected), + }; + _textBlocks.Clear(); + _textBlocks.Add(new TextBlock + { + Range = new TextRange + { + StartIndex = 0, + EndIndex = TextLength, + }, + Style = style, + Bounds = new Rectangle(Float2.Zero, font.MeasureText(Text)), + }); + } +#else + internal sealed class CustomCodeTextBox : TextBox + { +#endif + public override void Draw() + { + base.Draw(); + + // Draw border + if (!IsFocused) + Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.BorderNormal); + } + } + internal sealed class CustomCodeNode : SurfaceNode { private Rectangle _resizeButtonRect; private Float2 _startResizingSize; private Float2 _startResizingCornerOffset; private bool _isResizing; + private CustomCodeTextBox _textBox; private int SizeValueIndex => Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array @@ -280,6 +322,26 @@ namespace FlaxEditor.Surface.Archetypes public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) { + Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size; + if (nodeArch.TypeID == 8) + { + pos += new Float2(60, 0); + size = new Float2(172, 200); + } + else + { + pos += new Float2(0, 40); + size = new Float2(300, 200); + } + _textBox = new CustomCodeTextBox + { + IsMultiline = true, + Location = pos, + Size = size, + Parent = this, + AnchorMax = Float2.One, + }; + _textBox.EditEnd += () => SetValue(0, _textBox.Text); } public override bool CanSelect(ref Float2 location) @@ -291,8 +353,7 @@ namespace FlaxEditor.Surface.Archetypes { base.OnSurfaceLoaded(action); - var textBox = (TextBox)Children.First(x => x is TextBox); - textBox.AnchorMax = Float2.One; + _textBox.Text = (string)Values[0]; var size = SizeValue; if (Surface != null && Surface.GridSnappingEnabled) @@ -306,6 +367,7 @@ namespace FlaxEditor.Surface.Archetypes var size = SizeValue; Resize(size.X, size.Y); + _textBox.Text = (string)Values[0]; } protected override void UpdateRectangles() @@ -579,8 +641,6 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(1, "Output1", typeof(Float4), 9), NodeElementArchetype.Factory.Output(2, "Output2", typeof(Float4), 10), NodeElementArchetype.Factory.Output(3, "Output3", typeof(Float4), 11), - - NodeElementArchetype.Factory.TextBox(60, 0, 175, 200, 0), } }, new NodeArchetype @@ -1038,7 +1098,6 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Text(20, 0, "Enabled"), NodeElementArchetype.Factory.Text(0, 20, "Location"), NodeElementArchetype.Factory.Enum(50, 20, 120, 2, typeof(MaterialTemplateInputsMapping)), - NodeElementArchetype.Factory.TextBox(0, 40, 300, 200, 0), } }, new NodeArchetype diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index 4d9159ea9..6d40c3f78 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -156,7 +156,7 @@ Object::~Object() { #if BUILD_DEBUG // Prevent removing object that is still reverenced by the removal service - ASSERT(!ObjectsRemovalService::IsInPool(this)); + //ASSERT(!ObjectsRemovalService::IsInPool(this)); #endif } diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index b417854d7..f7726bf56 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -57,7 +57,7 @@ namespace FlaxEngine.GUI { base.OnSizeChanged(); - // Refresh textblocks since thos emight depend on control size (eg. align right) + // Refresh textblocks since those might depend on control size (eg. align right) UpdateTextBlocks(); } } From 3dc7546dd4907b69a206c20c07166295c659a35e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 27 Jun 2025 19:06:25 +0200 Subject: [PATCH 69/84] Fix crash when constant buffer is unused by shader but still exists --- Source/Engine/Renderer/AmbientOcclusionPass.cpp | 10 +--------- Source/Engine/Renderer/AntiAliasing/FXAA.cpp | 6 +----- Source/Engine/Renderer/AntiAliasing/SMAA.cpp | 8 +------- Source/Engine/Renderer/AntiAliasing/TAA.cpp | 6 +----- Source/Engine/Renderer/AtmospherePreCompute.cpp | 6 +----- Source/Engine/Renderer/ColorGradingPass.cpp | 8 +------- .../Renderer/ContrastAdaptiveSharpeningPass.cpp | 8 +------- Source/Engine/Renderer/DepthOfFieldPass.cpp | 8 +------- Source/Engine/Renderer/EyeAdaptationPass.cpp | 8 +------- Source/Engine/Renderer/HistogramPass.cpp | 8 +------- Source/Engine/Renderer/LightPass.cpp | 14 ++------------ Source/Engine/Renderer/MotionBlurPass.cpp | 10 +--------- Source/Engine/Renderer/PostProcessingPass.cpp | 14 ++------------ Source/Engine/Renderer/ProbesRenderer.cpp | 6 +----- Source/Engine/Renderer/ReflectionsPass.cpp | 8 +------- Source/Engine/Renderer/RendererPass.h | 1 + .../Engine/Renderer/ScreenSpaceReflectionsPass.cpp | 8 +------- Source/Engine/Renderer/ShadowsPass.cpp | 8 +------- Source/Engine/Renderer/Utils/BitonicSort.cpp | 8 +------- Source/Engine/Renderer/Utils/MultiScaler.cpp | 8 +------- Source/Engine/Renderer/VolumetricFogPass.cpp | 14 ++------------ 21 files changed, 24 insertions(+), 151 deletions(-) diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.cpp b/Source/Engine/Renderer/AmbientOcclusionPass.cpp index 5f181ab97..08a8cefcb 100644 --- a/Source/Engine/Renderer/AmbientOcclusionPass.cpp +++ b/Source/Engine/Renderer/AmbientOcclusionPass.cpp @@ -91,17 +91,9 @@ bool AmbientOcclusionPass::setupResources() { // Check shader if (!_shader->IsLoaded()) - { return true; - } const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(ASSAOConstants)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, ASSAOConstants); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, ASSAOConstants); // Create pipeline states GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp index 41926e2d6..00dfb0cdd 100644 --- a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp @@ -36,11 +36,7 @@ bool FXAA::setupResources() return true; } const auto shader = _shader->GetShader(); - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); GPUPipelineState::Description psDesc; if (!_psFXAA.IsValid()) diff --git a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp index 25007ad7d..2414118d3 100644 --- a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp @@ -45,13 +45,7 @@ bool SMAA::setupResources() return true; } const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.cpp b/Source/Engine/Renderer/AntiAliasing/TAA.cpp index 8bbb6ba81..b772eb45e 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/TAA.cpp @@ -37,11 +37,7 @@ bool TAA::setupResources() if (!_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); if (!_psTAA) _psTAA = GPUDevice::Instance->CreatePipelineState(); GPUPipelineState::Description psDesc; diff --git a/Source/Engine/Renderer/AtmospherePreCompute.cpp b/Source/Engine/Renderer/AtmospherePreCompute.cpp index 595ebcca5..b796e828b 100644 --- a/Source/Engine/Renderer/AtmospherePreCompute.cpp +++ b/Source/Engine/Renderer/AtmospherePreCompute.cpp @@ -166,11 +166,7 @@ bool init() } auto shader = _shader->GetShader(); ASSERT(shader->GetCB(0) != nullptr); - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stages _psTransmittance = GPUDevice::Instance->CreatePipelineState(); diff --git a/Source/Engine/Renderer/ColorGradingPass.cpp b/Source/Engine/Renderer/ColorGradingPass.cpp index 43b49f091..322e7d591 100644 --- a/Source/Engine/Renderer/ColorGradingPass.cpp +++ b/Source/Engine/Renderer/ColorGradingPass.cpp @@ -89,13 +89,7 @@ bool ColorGradingPass::setupResources() if (!_shader || !_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stages GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.cpp b/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.cpp index 6a6fce521..3231c32f8 100644 --- a/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.cpp +++ b/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.cpp @@ -48,13 +48,7 @@ bool ContrastAdaptiveSharpeningPass::setupResources() if (!_shader || !_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stage auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index 25d9ea94f..2c3dc36f8 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -117,13 +117,7 @@ bool DepthOfFieldPass::setupResources() if (!_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stages GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp index 84e826e36..3cf44af1b 100644 --- a/Source/Engine/Renderer/EyeAdaptationPass.cpp +++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp @@ -258,13 +258,7 @@ bool EyeAdaptationPass::setupResources() if (!_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(EyeAdaptationData)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, EyeAdaptationData); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, EyeAdaptationData); // Create pipeline stages GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/HistogramPass.cpp b/Source/Engine/Renderer/HistogramPass.cpp index 96f0cca73..9b6e71a77 100644 --- a/Source/Engine/Renderer/HistogramPass.cpp +++ b/Source/Engine/Renderer/HistogramPass.cpp @@ -113,13 +113,7 @@ bool HistogramPass::setupResources() if (!_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(HistogramData)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, HistogramData); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, HistogramData); _csClearHistogram = shader->GetCS("CS_ClearHistogram"); _csGenerateHistogram = shader->GetCS("CS_GenerateHistogram"); diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 1371a53ee..ee38ee2ac 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -65,18 +65,8 @@ bool LightPass::setupResources() if (!_sphereModel->CanBeRendered() || !_shader->IsLoaded()) return true; auto shader = _shader->GetShader(); - - // Validate shader constant buffers sizes - if (shader->GetCB(0)->GetSize() != sizeof(PerLight)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, PerLight); - return true; - } - if (shader->GetCB(1)->GetSize() != sizeof(PerFrame)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 1, PerFrame); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, PerLight); + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 1, PerFrame); // Create pipeline stages GPUPipelineState::Description psDesc; diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index d8ce05de1..3077e4cdb 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -80,17 +80,9 @@ bool MotionBlurPass::setupResources() { // Check shader if (!_shader->IsLoaded()) - { return true; - } const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 9192c8ca4..bc7a1b820 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -98,18 +98,8 @@ bool PostProcessingPass::setupResources() if (!_shader->IsLoaded()) return true; auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } - if (shader->GetCB(1)->GetSize() != sizeof(GaussianBlurData)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 1, GaussianBlurData); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 1, GaussianBlurData); // Create pipeline stages GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 70705b8a8..ee72afe72 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -231,11 +231,7 @@ bool ProbesRenderer::Init() if (!_shader->IsLoaded()) return false; const auto shader = _shader->GetShader(); - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stages _psFilterFace = GPUDevice::Instance->CreatePipelineState(); diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index 631543010..5aa8404ab 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -281,13 +281,7 @@ bool ReflectionsPass::setupResources() if (!_sphereModel->CanBeRendered() || !_preIntegratedGF->IsLoaded() || !_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stages GPUPipelineState::Description psDesc; diff --git a/Source/Engine/Renderer/RendererPass.h b/Source/Engine/Renderer/RendererPass.h index 25d887883..32d3b86b9 100644 --- a/Source/Engine/Renderer/RendererPass.h +++ b/Source/Engine/Renderer/RendererPass.h @@ -113,3 +113,4 @@ class RendererPass : public Singleton, public RendererPassBase }; #define REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, index, dataType) LOG(Fatal, "Shader {0} has incorrect constant buffer {1} size: {2} bytes. Expected: {3} bytes", shader->ToString(), index, shader->GetCB(index)->GetSize(), sizeof(dataType)); +#define CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, index, dataType) if (shader->GetCB(index)->GetSize() != sizeof(dataType) && shader->GetCB(index)->GetSize() != 0) { REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, index, dataType); return true; } diff --git a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp index 55c6d79f2..454540eec 100644 --- a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp +++ b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp @@ -89,13 +89,7 @@ bool ScreenSpaceReflectionsPass::setupResources() if (!_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stages GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 3937f8554..5cf90876a 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -507,13 +507,7 @@ bool ShadowsPass::setupResources() if (!_sphereModel->CanBeRendered() || !_shader->IsLoaded()) return true; auto shader = _shader->GetShader(); - - // Validate shader constant buffers sizes - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stages GPUPipelineState::Description psDesc; diff --git a/Source/Engine/Renderer/Utils/BitonicSort.cpp b/Source/Engine/Renderer/Utils/BitonicSort.cpp index 73a310832..babc058e2 100644 --- a/Source/Engine/Renderer/Utils/BitonicSort.cpp +++ b/Source/Engine/Renderer/Utils/BitonicSort.cpp @@ -59,14 +59,8 @@ bool BitonicSort::setupResources() if (!_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size _cb = shader->GetCB(0); - if (_cb->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Cache compute shaders _indirectArgsCS = shader->GetCS("CS_IndirectArgs"); diff --git a/Source/Engine/Renderer/Utils/MultiScaler.cpp b/Source/Engine/Renderer/Utils/MultiScaler.cpp index 3f812ea77..ae5633834 100644 --- a/Source/Engine/Renderer/Utils/MultiScaler.cpp +++ b/Source/Engine/Renderer/Utils/MultiScaler.cpp @@ -41,13 +41,7 @@ bool MultiScaler::setupResources() if (!_shader->IsLoaded()) return true; const auto shader = _shader->GetShader(); - - // Validate shader constant buffer size - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline states GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index c56812c72..b7e57c2bb 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -53,19 +53,9 @@ bool VolumetricFogPass::setupResources() if (!_shader->IsLoaded()) return true; auto shader = _shader->GetShader(); - - // Validate shader constant buffers sizes - if (shader->GetCB(0)->GetSize() != sizeof(Data)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // CB1 is used for per-draw info (ObjectIndex) - if (shader->GetCB(2)->GetSize() != sizeof(PerLight)) - { - REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 2, PerLight); - return true; - } + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 2, PerLight); // Cache compute shaders _csInitialize = shader->GetCS("CS_Initialize"); From bdd7bae4591360f430758fc820166000c796fb2b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Jun 2025 13:51:59 +0200 Subject: [PATCH 70/84] Add new Custom Lit shading model for custom lighting in materials (eg. Cel Shading) --- .../Features/ForwardShading.hlsl | 10 ++++++-- Source/Editor/Surface/Archetypes/Material.cs | 7 +++--- Source/Engine/Content/Assets/Material.cpp | 10 ++++---- .../Materials/DeferredMaterialShader.cpp | 2 ++ .../Engine/Graphics/Materials/MaterialInfo.h | 5 ++++ .../MaterialGenerator/MaterialGenerator.cpp | 23 ++++++++++--------- Source/Shaders/LightingCommon.hlsl | 18 +++++++++++++-- 7 files changed, 53 insertions(+), 22 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 2db55111b..263859075 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -28,6 +28,13 @@ TextureCube SkyLightTexture : register(t__SRV__); Buffer ShadowsBuffer : register(t__SRV__); Texture2D ShadowMap : register(t__SRV__); @4// Forward Shading: Utilities +// Public accessors for lighting data, use them as data binding might change but those methods will remain. +LightData GetDirectionalLight() { return DirectionalLight; } +LightData GetSkyLight() { return SkyLight; } +ProbeData GetEnvironmentProbe() { return EnvironmentProbe; } +ExponentialHeightFogData GetExponentialHeightFog() { return ExponentialHeightFog; } +uint GetLocalLightsCount() { return LocalLightsCount; } +LightData GetLocalLight(uint i) { return LocalLights[i]; } @5// Forward Shading: Shaders // Pixel Shader function for Forward Pass @@ -76,9 +83,8 @@ void PS_Forward( gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; // Calculate lighting from a single directional light - float4 shadowMask = 1.0f; ShadowSample shadow = SampleDirectionalLightShadow(DirectionalLight, ShadowsBuffer, ShadowMap, gBuffer); - shadowMask = GetShadowMask(shadow); + float4 shadowMask = GetShadowMask(shadow); float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); // Calculate lighting from sky light diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index c9066eaa5..e46038639 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -124,7 +124,8 @@ namespace FlaxEditor.Surface.Archetypes case MaterialDomain.Particle: case MaterialDomain.Deformable: { - bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit; + bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit && info.ShadingModel != MaterialShadingModel.CustomLit; + bool isOpaque = info.BlendMode == MaterialBlendMode.Opaque; bool withTess = info.TessellationMode != TessellationMethod.None; GetBox(MaterialNodeBoxes.Color).IsActive = isNotUnlit; @@ -135,8 +136,8 @@ namespace FlaxEditor.Surface.Archetypes GetBox(MaterialNodeBoxes.Roughness).IsActive = isNotUnlit; GetBox(MaterialNodeBoxes.AmbientOcclusion).IsActive = isNotUnlit; GetBox(MaterialNodeBoxes.Normal).IsActive = isNotUnlit; - GetBox(MaterialNodeBoxes.Opacity).IsActive = info.ShadingModel == MaterialShadingModel.Subsurface || info.ShadingModel == MaterialShadingModel.Foliage || info.BlendMode != MaterialBlendMode.Opaque; - GetBox(MaterialNodeBoxes.Refraction).IsActive = info.BlendMode != MaterialBlendMode.Opaque; + GetBox(MaterialNodeBoxes.Opacity).IsActive = info.ShadingModel == MaterialShadingModel.Subsurface || info.ShadingModel == MaterialShadingModel.Foliage || !isOpaque; + GetBox(MaterialNodeBoxes.Refraction).IsActive = !isOpaque; GetBox(MaterialNodeBoxes.PositionOffset).IsActive = true; GetBox(MaterialNodeBoxes.TessellationMultiplier).IsActive = withTess; GetBox(MaterialNodeBoxes.WorldDisplacement).IsActive = withTess; diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index ce457c862..1e36b36ae 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -414,16 +414,18 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) // Prepare auto& info = _shaderHeader.Material.Info; const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable; + const bool isOpaque = info.BlendMode == MaterialBlendMode::Opaque; const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage; - const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; + const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && !isOpaque) || info.Domain == MaterialDomain::Particle; const bool useTess = info.TessellationMode != TessellationMethod::None && RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable; const bool useDistortion = (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) && - info.BlendMode != MaterialBlendMode::Opaque && + !isOpaque && EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseRefraction) && (info.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None; + const MaterialShadingModel shadingModel = info.ShadingModel == MaterialShadingModel::CustomLit ? MaterialShadingModel::Unlit : info.ShadingModel; // @formatter:off static const char* Numbers[] = @@ -435,7 +437,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) // Setup shader macros options.Macros.Add({ "MATERIAL_DOMAIN", Numbers[(int32)info.Domain] }); options.Macros.Add({ "MATERIAL_BLEND", Numbers[(int32)info.BlendMode] }); - options.Macros.Add({ "MATERIAL_SHADING_MODEL", Numbers[(int32)info.ShadingModel] }); + options.Macros.Add({ "MATERIAL_SHADING_MODEL", Numbers[(int32)shadingModel] }); options.Macros.Add({ "MATERIAL_MASKED", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseMask) ? 1 : 0] }); options.Macros.Add({ "DECAL_BLEND_MODE", Numbers[(int32)info.DecalBlendingMode] }); options.Macros.Add({ "USE_EMISSIVE", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseEmissive) ? 1 : 0] }); @@ -492,7 +494,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] }); options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] }); options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] }); - options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); + options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && isOpaque ? 1 : 0] }); options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] }); #endif } diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index c15ff5ef2..4e9622a01 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -42,6 +42,8 @@ void DeferredMaterialShader::Bind(BindParameters& params) // Setup features const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv); + if (_info.ShadingModel == MaterialShadingModel::CustomLit) + ForwardShadingFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.h b/Source/Engine/Graphics/Materials/MaterialInfo.h index 2a57b5f3d..69a9bd0a6 100644 --- a/Source/Engine/Graphics/Materials/MaterialInfo.h +++ b/Source/Engine/Graphics/Materials/MaterialInfo.h @@ -103,6 +103,11 @@ API_ENUM() enum class MaterialShadingModel : byte /// The foliage material. Intended for foliage materials like leaves and grass that need light scattering to transport simulation through the thin object. /// Foliage = 3, + + /// + /// The custom lit shader that calculates own lighting such as Cel Shading. It has access to the scene lights data during both GBuffer and Forward pass rendering. + /// + CustomLit = 5, }; /// diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 5950676a3..2aea40c94 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -184,28 +184,29 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo return true; \ } \ } + const bool isOpaque = materialInfo.BlendMode == MaterialBlendMode::Opaque; switch (baseLayer->Domain) { case MaterialDomain::Surface: if (materialInfo.TessellationMode != TessellationMethod::None) ADD_FEATURE(TessellationFeature); - if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + if (isOpaque) ADD_FEATURE(MotionVectorsFeature); - if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + if (isOpaque) ADD_FEATURE(LightmapFeature); - if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + if (isOpaque) ADD_FEATURE(DeferredShadingFeature); - if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None) + if (!isOpaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None) ADD_FEATURE(DistortionFeature); - if (materialInfo.BlendMode != MaterialBlendMode::Opaque && EnumHasAnyFlags(materialInfo.FeaturesFlags, MaterialFeaturesFlags::GlobalIllumination)) + if (!isOpaque && EnumHasAnyFlags(materialInfo.FeaturesFlags, MaterialFeaturesFlags::GlobalIllumination)) { ADD_FEATURE(GlobalIlluminationFeature); - // SDF Reflections is only valid when both GI and SSR is enabled - if (materialInfo.BlendMode != MaterialBlendMode::Opaque && EnumHasAnyFlags(materialInfo.FeaturesFlags, MaterialFeaturesFlags::ScreenSpaceReflections)) + // SDF Reflections is only valid when both GI and SSR are enabled + if (EnumHasAnyFlags(materialInfo.FeaturesFlags, MaterialFeaturesFlags::ScreenSpaceReflections)) ADD_FEATURE(SDFReflectionsFeature); } - if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + if (materialInfo.BlendMode != MaterialBlendMode::Opaque || materialInfo.ShadingModel == MaterialShadingModel::CustomLit) ADD_FEATURE(ForwardShadingFeature); break; case MaterialDomain::Terrain: @@ -215,16 +216,16 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ADD_FEATURE(DeferredShadingFeature); break; case MaterialDomain::Particle: - if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None) + if (!isOpaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None) ADD_FEATURE(DistortionFeature); - if (materialInfo.BlendMode != MaterialBlendMode::Opaque && EnumHasAnyFlags(materialInfo.FeaturesFlags, MaterialFeaturesFlags::GlobalIllumination)) + if (!isOpaque && EnumHasAnyFlags(materialInfo.FeaturesFlags, MaterialFeaturesFlags::GlobalIllumination)) ADD_FEATURE(GlobalIlluminationFeature); ADD_FEATURE(ForwardShadingFeature); break; case MaterialDomain::Deformable: if (materialInfo.TessellationMode != TessellationMethod::None) ADD_FEATURE(TessellationFeature); - if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + if (isOpaque) ADD_FEATURE(DeferredShadingFeature); if (materialInfo.BlendMode != MaterialBlendMode::Opaque) ADD_FEATURE(ForwardShadingFeature); diff --git a/Source/Shaders/LightingCommon.hlsl b/Source/Shaders/LightingCommon.hlsl index 807d6a71d..f09572310 100644 --- a/Source/Shaders/LightingCommon.hlsl +++ b/Source/Shaders/LightingCommon.hlsl @@ -62,8 +62,8 @@ void GetRadialLightAttenuation( float distanceBiasSqr, float3 toLight, float3 L, - inout float NoL, - inout float attenuation) + out float NoL, + out float attenuation) { // Distance attenuation if (lightData.InverseSquared) @@ -104,6 +104,20 @@ void GetRadialLightAttenuation( } } +// Calculates radial light (point or spot) attenuation factors (distance, spot and radius mask) +void GetRadialLightAttenuation( + LightData lightData, + bool isSpotLight, + float3 toLight, + float3 N, + out float NoL, + out float attenuation) +{ + float distanceSqr = dot(toLight, toLight); + float3 L = toLight * rsqrt(distanceSqr); + GetRadialLightAttenuation(lightData, isSpotLight, N, distanceSqr, 1, toLight, L, NoL, attenuation); +} + // Find representative incoming light direction and energy modification float AreaLightSpecular(LightData lightData, float roughness, inout float3 toLight, inout float3 L, float3 V, half3 N) { From f126a83b797c1fd3dda98f3438079c81d33dcde3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Jun 2025 13:52:29 +0200 Subject: [PATCH 71/84] Fix graphical issues when batching materials that use Forward Shading for instancing --- Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp | 5 ++++- Source/Engine/Graphics/Materials/DeferredMaterialShader.h | 1 + Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 4e9622a01..5f0abad33 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -29,7 +29,7 @@ bool DeferredMaterialShader::CanUseLightmap() const bool DeferredMaterialShader::CanUseInstancing(InstancingHandler& handler) const { handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, }; - return true; + return _instanced; } void DeferredMaterialShader::Bind(BindParameters& params) @@ -114,6 +114,9 @@ void DeferredMaterialShader::Unload() bool DeferredMaterialShader::Load() { + // TODO: support instancing when using ForwardShadingFeature + _instanced = _info.BlendMode == MaterialBlendMode::Opaque && _info.ShadingModel != MaterialShadingModel::CustomLit; + bool failed = false; auto psDesc = GPUPipelineState::Description::Default; psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None; diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h index 4d01c4d4a..ebfd54ecb 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h @@ -65,6 +65,7 @@ private: private: Cache _cache; Cache _cacheInstanced; + bool _instanced; public: DeferredMaterialShader(const StringView& name) diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index 4ed8e6b86..a966507d8 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -25,7 +25,7 @@ DrawPass ForwardMaterialShader::GetDrawModes() const bool ForwardMaterialShader::CanUseInstancing(InstancingHandler& handler) const { handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, }; - return true; + return false; // TODO: support instancing when using ForwardShadingFeature } void ForwardMaterialShader::Bind(BindParameters& params) From 43d11264f82ac2ffb3b4f94d77da427f32a05ac2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Jun 2025 19:16:23 +0200 Subject: [PATCH 72/84] Fix asset references to use separate lightweight locking instead of full asset mutex --- Source/Engine/Content/Asset.cpp | 8 ++++---- Source/Engine/Content/Asset.h | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 5a9e62993..559a673da 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -242,9 +242,8 @@ void Asset::AddReference(IAssetReference* ref, bool week) if (ref) { //PROFILE_MEM(EngineDelegate); // Include references tracking memory within Delegate memory - Locker.Lock(); + ScopeLock lock(_referencesLocker); _references.Add(ref); - Locker.Unlock(); } } @@ -257,9 +256,8 @@ void Asset::RemoveReference(IAssetReference* ref, bool week) { if (ref) { - Locker.Lock(); + ScopeLock lock(_referencesLocker); _references.Remove(ref); - Locker.Unlock(); } if (!week) Platform::InterlockedDecrement(&_refCount); @@ -681,6 +679,7 @@ void Asset::onLoaded_MainThread() ASSERT(IsInMainThread()); // Send event + ScopeLock lock(_referencesLocker); for (const auto& e : _references) e.Item->OnAssetLoaded(this, this); OnLoaded(this); @@ -696,6 +695,7 @@ void Asset::onUnload_MainThread() CancelStreaming(); // Send event + ScopeLock lock(_referencesLocker); for (const auto& e : _references) e.Item->OnAssetUnloaded(this, this); OnUnloaded(this); diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index bb0fbe490..c838eddf8 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -7,6 +7,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Threading/ConcurrentSystemLocker.h" #include "Config.h" #include "Types.h" @@ -63,6 +64,7 @@ protected: int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved) HashSet _references; + CriticalSection _referencesLocker; // TODO: convert into a single interlocked exchange for the current thread owning lock public: /// From 78d519cb9a71ab42a506c0f5018b6d417f2bf426 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Jun 2025 19:16:41 +0200 Subject: [PATCH 73/84] Fix `ConcurrentSystemLocker` to have exclusive lock as an option --- Source/Engine/Level/Scene/SceneRendering.cpp | 6 +++--- Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 8 +++++--- Source/Engine/Threading/ConcurrentSystemLocker.cpp | 6 +++--- Source/Engine/Threading/ConcurrentSystemLocker.h | 6 +++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 4a88703ae..e55dbd43f 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -127,7 +127,7 @@ void SceneRendering::CollectPostFxVolumes(RenderContext& renderContext) void SceneRendering::Clear() { - ConcurrentSystemLocker::WriteScope lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker, true); for (auto* listener : _listeners) { listener->OnSceneRenderingClear(this); @@ -149,7 +149,7 @@ void SceneRendering::AddActor(Actor* a, int32& key) return; PROFILE_MEM(Graphics); const int32 category = a->_drawCategory; - ConcurrentSystemLocker::WriteScope lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker, true); auto& list = Actors[category]; if (FreeActors[category].HasItems()) { @@ -193,7 +193,7 @@ void SceneRendering::UpdateActor(Actor* a, int32& key, ISceneRenderingListener:: void SceneRendering::RemoveActor(Actor* a, int32& key) { const int32 category = a->_drawCategory; - ConcurrentSystemLocker::WriteScope lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker, true); auto& list = Actors[category]; if (list.Count() > key) // Ignore invalid key softly (eg. list after batch clear during scene unload) { diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 56dd196a5..e0d227b6b 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -396,7 +396,7 @@ public: { if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { - ConcurrentSystemLocker::WriteScope lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker, true); OnSceneRenderingDirty(a->GetBox()); } } @@ -405,7 +405,7 @@ public: { if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { - ConcurrentSystemLocker::WriteScope lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker, true); OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); OnSceneRenderingDirty(a->GetBox()); } @@ -415,13 +415,14 @@ public: { if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { - ConcurrentSystemLocker::WriteScope lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker, true); OnSceneRenderingDirty(a->GetBox()); } } void OnSceneRenderingClear(SceneRendering* scene) override { + ConcurrentSystemLocker::WriteScope lock(Locker, true); for (auto& cascade : Cascades) cascade.StaticChunks.Clear(); } @@ -719,6 +720,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex } sdfData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global SDF"); + ConcurrentSystemLocker::WriteScope lock(sdfData.Locker); // Setup options int32 resolution, cascadesCount, resolutionMip; diff --git a/Source/Engine/Threading/ConcurrentSystemLocker.cpp b/Source/Engine/Threading/ConcurrentSystemLocker.cpp index c8debb561..d936f8307 100644 --- a/Source/Engine/Threading/ConcurrentSystemLocker.cpp +++ b/Source/Engine/Threading/ConcurrentSystemLocker.cpp @@ -8,7 +8,7 @@ ConcurrentSystemLocker::ConcurrentSystemLocker() _counters[0] = _counters[1] = 0; } -void ConcurrentSystemLocker::Begin(bool write) +void ConcurrentSystemLocker::Begin(bool write, bool exclusively) { volatile int64* thisCounter = &_counters[write]; volatile int64* otherCounter = &_counters[!write]; @@ -22,8 +22,8 @@ RETRY: goto RETRY; } - // Writers have to check themselves to (one write at the same time - just like a mutex) - if (write && Platform::AtomicRead(thisCounter) != 0) + // Writers might want to check themselves for a single writer at the same time - just like a mutex + if (exclusively && Platform::AtomicRead(thisCounter) != 0) { // Someone else is doing opposite operation so wait for it's end Platform::Sleep(0); diff --git a/Source/Engine/Threading/ConcurrentSystemLocker.h b/Source/Engine/Threading/ConcurrentSystemLocker.h index dd214a308..031b7e685 100644 --- a/Source/Engine/Threading/ConcurrentSystemLocker.h +++ b/Source/Engine/Threading/ConcurrentSystemLocker.h @@ -17,7 +17,7 @@ public: NON_COPYABLE(ConcurrentSystemLocker); ConcurrentSystemLocker(); - void Begin(bool write); + void Begin(bool write, bool exclusively = false); void End(bool write); public: @@ -26,10 +26,10 @@ public: { NON_COPYABLE(Scope); - Scope(ConcurrentSystemLocker& locker) + Scope(ConcurrentSystemLocker& locker, bool exclusively = false) : _locker(locker) { - _locker.Begin(Write); + _locker.Begin(Write, exclusively); } ~Scope() From 448eb48c230dd1c7c2591798d5ce4a993257ddfe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Jun 2025 20:02:24 +0200 Subject: [PATCH 74/84] Fix fog to draw Fog Cutoff Distance via a plane, not sphere test Add support for negative Fog Cutoff Distance on fog to draw it in front of the camera Far Plane, no matter the setup. Fix hot-reloading Fog shader in Editor. --- .../Features/ForwardShading.hlsl | 2 +- Content/Editor/Particles/Smoke Material.flax | 4 +- Content/Shaders/Fog.flax | 4 +- .../Level/Actors/ExponentialHeightFog.cpp | 8 +-- .../Level/Actors/ExponentialHeightFog.h | 4 +- Source/Shaders/ExponentialHeightFog.hlsl | 9 ++- Source/Shaders/Fog.shader | 55 +++++++------------ 7 files changed, 39 insertions(+), 47 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 263859075..625a78e36 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -151,7 +151,7 @@ void PS_Forward( #if USE_FOG // Calculate exponential height fog - float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); + float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0, gBuffer.ViewPos.z); // Apply fog to the output color #if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index bd8c1c0e9..d5b8cb872 100644 --- a/Content/Editor/Particles/Smoke Material.flax +++ b/Content/Editor/Particles/Smoke Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6097a8ca31dbe7a985b5c512d2049d2d22c73175551965c75d6b360323505491 -size 38427 +oid sha256:a16a3fa5bed3bc8030c40fbe0e946f2bdec28745542bf08db1d7b4a43180f785 +size 38900 diff --git a/Content/Shaders/Fog.flax b/Content/Shaders/Fog.flax index 75590f84d..3f934412c 100644 --- a/Content/Shaders/Fog.flax +++ b/Content/Shaders/Fog.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7735a770a87483d4df5e4e653373067c26469de8088f071ca092ed3e797bf461 -size 2785 +oid sha256:e83f9dbbcf84550de09e7c63bbdd3acc6591cf6ba1bcce2a2699772122ae07f4 +size 2633 diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index d62aecac4..efb5351e7 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -41,11 +41,10 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext) && _shader->IsLoaded() && renderContext.View.IsPerspectiveProjection()) { - // Prepare if (_psFog.States[0] == nullptr) - { - // Create pipeline states _psFog.CreatePipelineStates(); + if (!_psFog.States[0]->IsValid()) + { GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; psDesc.DepthWriteEnable = false; psDesc.BlendMode.BlendEnable = true; @@ -59,6 +58,7 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext) if (_psFog.Create(psDesc, _shader->GetShader(), "PS_Fog")) { LOG(Warning, "Cannot create graphics pipeline state object for '{0}'.", ToString()); + return; } } @@ -160,7 +160,7 @@ void ExponentialHeightFog::GetExponentialHeightFogData(const RenderView& view, S result.FogAtViewPosition = density * Math::Pow(2.0f, Math::Clamp(-heightFalloff * (viewHeight - height), -125.f, 126.f)); result.StartDistance = StartDistance; result.FogMinOpacity = 1.0f - FogMaxOpacity; - result.FogCutoffDistance = FogCutoffDistance; + result.FogCutoffDistance = FogCutoffDistance >= 0 ? FogCutoffDistance : view.Far + FogCutoffDistance; if (useDirectionalLightInscattering) { result.InscatteringLightDirection = -DirectionalInscatteringLight->GetDirection(); diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h index 0b442ba9f..c0e5407d2 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.h +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h @@ -55,9 +55,9 @@ public: float StartDistance = 0.0f; /// - /// Scene elements past this distance will not have fog applied. This is useful for excluding skyboxes which already have fog baked in. Setting this value to 0 disables it. + /// Scene elements past this distance will not have fog applied. This is useful for excluding skyboxes which already have fog baked in. Setting this value to 0 disables it. Negative value sets the cutoff distance relative to the far plane of the camera. /// - API_FIELD(Attributes="EditorOrder(60), DefaultValue(0.0f), Limit(0), EditorDisplay(\"Exponential Height Fog\")") + API_FIELD(Attributes="EditorOrder(60), DefaultValue(0.0f), EditorDisplay(\"Exponential Height Fog\")") float FogCutoffDistance = 0.0f; public: diff --git a/Source/Shaders/ExponentialHeightFog.hlsl b/Source/Shaders/ExponentialHeightFog.hlsl index 2e34936eb..f6fb918f5 100644 --- a/Source/Shaders/ExponentialHeightFog.hlsl +++ b/Source/Shaders/ExponentialHeightFog.hlsl @@ -29,7 +29,7 @@ struct ExponentialHeightFogData float StartDistance; }; -float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance) +float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance, float sceneDistance) { float3 cameraToPos = posWS - camWS; float cameraToPosSqr = dot(cameraToPos, cameraToPos); @@ -78,7 +78,7 @@ float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, fl // Disable fog after a certain distance FLATTEN - if (exponentialHeightFog.FogCutoffDistance > 0 && cameraToPosLen > exponentialHeightFog.FogCutoffDistance) + if (exponentialHeightFog.FogCutoffDistance > 0 && sceneDistance > exponentialHeightFog.FogCutoffDistance) { expFogFactor = 1; directionalInscattering = 0; @@ -87,4 +87,9 @@ float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, fl return float4(inscatteringColor * (1.0f - expFogFactor) + directionalInscattering, expFogFactor); } +float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance) +{ + return GetExponentialHeightFog(exponentialHeightFog, posWS, camWS, skipDistance, distance(posWS, camWS)); +} + #endif diff --git a/Source/Shaders/Fog.shader b/Source/Shaders/Fog.shader index 7dcc679ec..dfea921cc 100644 --- a/Source/Shaders/Fog.shader +++ b/Source/Shaders/Fog.shader @@ -24,41 +24,17 @@ Texture2D Depth : register(t0); Texture3D IntegratedLightScattering : register(t1); #endif -// Get world space position at given pixel coordinate -float3 GetWorldPos(float2 uv) -{ - float depth = SAMPLE_RT(Depth, uv).r; - GBufferData gBufferData = GetGBufferData(); - float3 viewPos = GetViewPos(gBufferData, uv, depth); - return mul(float4(viewPos, 1), gBufferData.InvViewMatrix).xyz; -} - -float4 CalculateCombinedFog(float3 posWS, float sceneDepth, float3 volumeUV) -{ - float skipDistance = 0; - -#if VOLUMETRIC_FOG - skipDistance = max(ExponentialHeightFog.VolumetricFogMaxDistance - 100, 0); -#endif - - float4 fog = GetExponentialHeightFog(ExponentialHeightFog, posWS, GBuffer.ViewPos, skipDistance); - -#if VOLUMETRIC_FOG - float4 volumetricFog = IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0); - fog = float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a); -#endif - - return fog; -} - META_PS(true, FEATURE_LEVEL_ES2) META_PERMUTATION_1(VOLUMETRIC_FOG=0) META_PERMUTATION_1(VOLUMETRIC_FOG=1) float4 PS_Fog(Quad_VS2PS input) : SV_Target0 { - // Calculate pixel world space position - float3 posWS = GetWorldPos(input.TexCoord); - float3 viewVector = posWS - GBuffer.ViewPos; + // Get world space position at given pixel coordinate + float rawDepth = SAMPLE_RT(Depth, input.TexCoord).r; + GBufferData gBufferData = GetGBufferData(); + float3 viewPos = GetViewPos(gBufferData, input.TexCoord, rawDepth); + float3 worldPos = mul(float4(viewPos, 1), gBufferData.InvViewMatrix).xyz; + float3 viewVector = worldPos - GBuffer.ViewPos; float sceneDepth = length(viewVector); // Calculate volumetric fog coordinates @@ -67,17 +43,28 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0 // Debug code #if VOLUMETRIC_FOG && 0 - volumeUV = posWS / 1000; + volumeUV = worldPos / 1000; if (!all(volumeUV >= 0 && volumeUV <= 1)) return 0; return float4(IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0).rgb, 1); //return float4(volumeUV, 1); - //return float4(posWS / 100, 1); + //return float4(worldPos / 100, 1); #endif - // Calculate fog color - float4 fog = CalculateCombinedFog(posWS, sceneDepth, volumeUV); + float skipDistance = 0; +#if VOLUMETRIC_FOG + skipDistance = max(ExponentialHeightFog.VolumetricFogMaxDistance - 100, 0); +#endif + + // Calculate exponential fog color + float4 fog = GetExponentialHeightFog(ExponentialHeightFog, worldPos, GBuffer.ViewPos, skipDistance, viewPos.z); + +#if VOLUMETRIC_FOG + // Sample volumetric fog and mix it in + float4 volumetricFog = IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0); + fog = float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a); +#endif return fog; } From 094a6562b87ce661fc549a5b53efe5abc5b40545 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Jul 2025 10:18:51 +0200 Subject: [PATCH 75/84] Refactor `ProbesRenderer` --- Source/Editor/Managed/ManagedEditor.cpp | 9 +- Source/Engine/Renderer/ProbesRenderer.cpp | 338 ++++++++++------------ Source/Engine/Renderer/ProbesRenderer.h | 70 +---- 3 files changed, 173 insertions(+), 244 deletions(-) diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index e270d08f8..c020fe7ce 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -13,6 +13,7 @@ #include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h" #include "Engine/Content/Assets/VisualScript.h" #include "Engine/Content/Content.h" +#include "Engine/Level/Actor.h" #include "Engine/CSG/CSGBuilder.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Renderer/ProbesRenderer.h" @@ -74,7 +75,7 @@ void OnLightmapsBuildFinished(bool failed) OnLightmapsBake(ShadowsOfMordor::BuildProgressStep::GenerateLightmapCharts, 0, 0, false); } -void OnBakeEvent(bool started, const ProbesRenderer::Entry& e) +void OnBakeEvent(bool started, Actor* e) { if (Internal_EnvProbeBake == nullptr) { @@ -82,7 +83,7 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e) ASSERT(Internal_EnvProbeBake); } - MObject* probeObj = e.Actor ? e.Actor->GetManagedInstance() : nullptr; + MObject* probeObj = e ? e->GetManagedInstance() : nullptr; MainThreadManagedInvokeAction::ParamsBuilder params; params.AddParam(started); @@ -90,12 +91,12 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e) MainThreadManagedInvokeAction::Invoke(Internal_EnvProbeBake, params); } -void OnRegisterBake(const ProbesRenderer::Entry& e) +void OnRegisterBake(Actor* e) { OnBakeEvent(true, e); } -void OnFinishBake(const ProbesRenderer::Entry& e) +void OnFinishBake(Actor* e) { OnBakeEvent(false, e); } diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index ee72afe72..ac19cd309 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -4,8 +4,8 @@ #include "Renderer.h" #include "ReflectionsPass.h" #include "Engine/Core/Config/GraphicsSettings.h" -#include "Engine/Threading/ThreadPoolTask.h" -#include "Engine/Content/Content.h" +#include "Engine/Engine/Time.h" +#include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Level/Actors/PointLight.h" #include "Engine/Level/Actors/EnvironmentProbe.h" @@ -14,28 +14,49 @@ #include "Engine/Level/LargeWorlds.h" #include "Engine/ContentExporters/AssetExporters.h" #include "Engine/Serialization/FileWriteStream.h" -#include "Engine/Engine/Time.h" +#include "Engine/Content/Content.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/AssetReference.h" #include "Engine/Graphics/Graphics.h" +#include "Engine/Graphics/PixelFormat.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/RenderTask.h" -#include "Engine/Engine/Engine.h" +#include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Threading/ThreadPoolTask.h" -/// -/// Custom task called after downloading probe texture data to save it. -/// +// Amount of frames to wait for data from probe update job +#define PROBES_RENDERER_LATENCY_FRAMES 1 + +struct ProbeEntry +{ + enum class Types + { + Invalid = 0, + EnvProbe = 1, + SkyLight = 2, + }; + + Types Type = Types::Invalid; + float Timeout = 0.0f; + ScriptingObjectReference Actor; + + bool UseTextureData() const; + int32 GetResolution() const; + PixelFormat GetFormat() const; +}; + +// Custom task called after downloading probe texture data to save it. class DownloadProbeTask : public ThreadPoolTask { private: GPUTexture* _texture; TextureData _data; - ProbesRenderer::Entry _entry; + ProbeEntry _entry; public: - DownloadProbeTask(GPUTexture* target, const ProbesRenderer::Entry& entry) + DownloadProbeTask(GPUTexture* target, const ProbeEntry& entry) : _texture(target) , _entry(entry) { @@ -48,23 +69,23 @@ public: bool Run() override { - if (_entry.Type == ProbesRenderer::EntryType::EnvProbe) + Actor* actor = _entry.Actor.Get(); + if (_entry.Type == ProbeEntry::Types::EnvProbe) { - if (_entry.Actor) - ((EnvironmentProbe*)_entry.Actor.Get())->SetProbeData(_data); + if (actor) + ((EnvironmentProbe*)actor)->SetProbeData(_data); } - else if (_entry.Type == ProbesRenderer::EntryType::SkyLight) + else if (_entry.Type == ProbeEntry::Types::SkyLight) { - if (_entry.Actor) - ((SkyLight*)_entry.Actor.Get())->SetProbeData(_data); + if (actor) + ((SkyLight*)actor)->SetProbeData(_data); } else { return true; } - ProbesRenderer::OnFinishBake(_entry); - + ProbesRenderer::OnFinishBake(actor); return false; } }; @@ -75,14 +96,17 @@ GPU_CB_STRUCT(Data { float SourceMipIndex; }); -namespace ProbesRendererImpl +class ProbesRendererService : public EngineService { - TimeSpan _lastProbeUpdate(0); - Array _probesToBake; +private: + bool _initDone = false; + bool _initFailed = false; - ProbesRenderer::Entry _current; + TimeSpan _lastProbeUpdate = TimeSpan(0); + Array _probesToBake; + + ProbeEntry _current; - bool _isReady = false; AssetReference _shader; GPUPipelineState* _psFilterFace = nullptr; SceneRenderTask* _task = nullptr; @@ -92,91 +116,52 @@ namespace ProbesRendererImpl GPUTexture* _skySHIrradianceMap = nullptr; uint64 _updateFrameNumber = 0; - FORCE_INLINE bool isUpdateSynced() - { - return _updateFrameNumber > 0 && _updateFrameNumber + PROBES_RENDERER_LATENCY_FRAMES <= Engine::FrameCount; - } -} - -using namespace ProbesRendererImpl; - -class ProbesRendererService : public EngineService -{ public: ProbesRendererService() : EngineService(TEXT("Probes Renderer"), 500) { } + bool LazyInit(); void Update() override; void Dispose() override; + + void Bake(const ProbeEntry& e); + void OnRender(RenderTask* task, GPUContext* context); }; ProbesRendererService ProbesRendererServiceInstance; -TimeSpan ProbesRenderer::ProbesUpdatedBreak(0, 0, 0, 0, 500); -TimeSpan ProbesRenderer::ProbesReleaseDataTime(0, 0, 0, 60); -Delegate ProbesRenderer::OnRegisterBake; -Delegate ProbesRenderer::OnFinishBake; +TimeSpan ProbesRenderer::UpdateDelay(0, 0, 0, 0, 100); +TimeSpan ProbesRenderer::ReleaseTimeout(0, 0, 0, 30); +Delegate ProbesRenderer::OnRegisterBake; +Delegate ProbesRenderer::OnFinishBake; void ProbesRenderer::Bake(EnvironmentProbe* probe, float timeout) { if (!probe || probe->IsUsingCustomProbe()) return; - - // Check if already registered for bake - for (int32 i = 0; i < _probesToBake.Count(); i++) - { - auto& p = _probesToBake[i]; - if (p.Type == EntryType::EnvProbe && p.Actor == probe) - { - p.Timeout = timeout; - return; - } - } - - // Register probe - Entry e; - e.Type = EntryType::EnvProbe; + ProbeEntry e; + e.Type = ProbeEntry::Types::EnvProbe; e.Actor = probe; e.Timeout = timeout; - _probesToBake.Add(e); - - // Fire event - if (e.UseTextureData()) - OnRegisterBake(e); + ProbesRendererServiceInstance.Bake(e); } void ProbesRenderer::Bake(SkyLight* probe, float timeout) { - ASSERT(probe && dynamic_cast(probe)); - - // Check if already registered for bake - for (int32 i = 0; i < _probesToBake.Count(); i++) - { - auto& p = _probesToBake[i]; - if (p.Type == EntryType::SkyLight && p.Actor == probe) - { - p.Timeout = timeout; - return; - } - } - - // Register probe - Entry e; - e.Type = EntryType::SkyLight; + if (!probe) + return; + ProbeEntry e; + e.Type = ProbeEntry::Types::SkyLight; e.Actor = probe; e.Timeout = timeout; - _probesToBake.Add(e); - - // Fire event - if (e.UseTextureData()) - OnRegisterBake(e); + ProbesRendererServiceInstance.Bake(e); } -bool ProbesRenderer::Entry::UseTextureData() const +bool ProbeEntry::UseTextureData() const { - if (Type == EntryType::EnvProbe && Actor) + if (Type == Types::EnvProbe && Actor) { switch (Actor.As()->UpdateMode) { @@ -187,12 +172,12 @@ bool ProbesRenderer::Entry::UseTextureData() const return true; } -int32 ProbesRenderer::Entry::GetResolution() const +int32 ProbeEntry::GetResolution() const { auto resolution = ProbeCubemapResolution::UseGraphicsSettings; - if (Type == EntryType::EnvProbe && Actor) + if (Type == Types::EnvProbe && Actor) resolution = ((EnvironmentProbe*)Actor.Get())->CubemapResolution; - else if (Type == EntryType::SkyLight) + else if (Type == Types::SkyLight) resolution = ProbeCubemapResolution::_128; if (resolution == ProbeCubemapResolution::UseGraphicsSettings) resolution = GraphicsSettings::Get()->DefaultProbeResolution; @@ -201,116 +186,83 @@ int32 ProbesRenderer::Entry::GetResolution() const return (int32)resolution; } -PixelFormat ProbesRenderer::Entry::GetFormat() const +PixelFormat ProbeEntry::GetFormat() const { return GraphicsSettings::Get()->UseHDRProbes ? PixelFormat::R11G11B10_Float : PixelFormat::R8G8B8A8_UNorm; } -int32 ProbesRenderer::GetBakeQueueSize() +bool ProbesRendererService::LazyInit() { - return _probesToBake.Count(); -} - -bool ProbesRenderer::HasReadyResources() -{ - return _isReady && _shader->IsLoaded(); -} - -bool ProbesRenderer::Init() -{ - if (_isReady) + if (_initDone || _initFailed) return false; // Load shader if (_shader == nullptr) { _shader = Content::LoadAsyncInternal(TEXT("Shaders/ProbesFilter")); - if (_shader == nullptr) - return true; + _initFailed = _shader == nullptr; + if (_initFailed) + return false; } if (!_shader->IsLoaded()) - return false; + return true; const auto shader = _shader->GetShader(); CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); // Create pipeline stages _psFilterFace = GPUDevice::Instance->CreatePipelineState(); - GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; { psDesc.PS = shader->GetPS("PS_FilterFace"); - if (_psFilterFace->Init(psDesc)) - return true; + _initFailed |= _psFilterFace->Init(psDesc); } // Init rendering pipeline - _output = GPUDevice::Instance->CreateTexture(TEXT("Output")); + _output = GPUDevice::Instance->CreateTexture(TEXT("ProbesRenderer.Output")); const int32 probeResolution = _current.GetResolution(); const PixelFormat probeFormat = _current.GetFormat(); - if (_output->Init(GPUTextureDescription::New2D(probeResolution, probeResolution, probeFormat))) - return true; + _initFailed |= _output->Init(GPUTextureDescription::New2D(probeResolution, probeResolution, probeFormat)); _task = New(); auto task = _task; + task->Order = -100; // Run before main view rendering (realtime probes will get smaller latency) task->Enabled = false; task->IsCustomRendering = true; task->Output = _output; auto& view = task->View; view.Flags = - ViewFlags::AO | - ViewFlags::GI | - ViewFlags::DirectionalLights | - ViewFlags::PointLights | - ViewFlags::SpotLights | - ViewFlags::SkyLights | - ViewFlags::Decals | - ViewFlags::Shadows | - ViewFlags::Sky | - ViewFlags::Fog; + ViewFlags::AO | + ViewFlags::GI | + ViewFlags::DirectionalLights | + ViewFlags::PointLights | + ViewFlags::SpotLights | + ViewFlags::SkyLights | + ViewFlags::Decals | + ViewFlags::Shadows | + ViewFlags::Sky | + ViewFlags::Fog; view.Mode = ViewMode::NoPostFx; view.IsOfflinePass = true; view.IsSingleFrame = true; view.StaticFlagsMask = view.StaticFlagsCompare = StaticFlags::ReflectionProbe; - view.MaxShadowsQuality = Quality::Low; task->IsCameraCut = true; task->Resize(probeResolution, probeResolution); - task->Render.Bind(OnRender); + task->Render.Bind(this); // Init render targets - _probe = GPUDevice::Instance->CreateTexture(TEXT("ProbesUpdate.Probe")); - if (_probe->Init(GPUTextureDescription::NewCube(probeResolution, probeFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews, 0))) - return true; - _tmpFace = GPUDevice::Instance->CreateTexture(TEXT("ProbesUpdate.TmpFace")); - if (_tmpFace->Init(GPUTextureDescription::New2D(probeResolution, probeResolution, 0, probeFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews))) - return true; + _probe = GPUDevice::Instance->CreateTexture(TEXT("ProbesRenderer.Probe")); + _initFailed |= _probe->Init(GPUTextureDescription::NewCube(probeResolution, probeFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews, 0)); + _tmpFace = GPUDevice::Instance->CreateTexture(TEXT("ProbesRenderer.TmpFace")); + _initFailed |= _tmpFace->Init(GPUTextureDescription::New2D(probeResolution, probeResolution, 0, probeFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews)); // Mark as ready - _isReady = true; + _initDone = true; return false; } -void ProbesRenderer::Release() -{ - if (!_isReady) - return; - ASSERT(_updateFrameNumber == 0); - - // Release GPU data - if (_output) - _output->ReleaseGPU(); - - // Release data - SAFE_DELETE_GPU_RESOURCE(_psFilterFace); - _shader = nullptr; - SAFE_DELETE_GPU_RESOURCE(_output); - SAFE_DELETE(_task); - SAFE_DELETE_GPU_RESOURCE(_probe); - SAFE_DELETE_GPU_RESOURCE(_tmpFace); - SAFE_DELETE_GPU_RESOURCE(_skySHIrradianceMap); - - _isReady = false; -} - void ProbesRendererService::Update() { + PROFILE_MEM(Graphics); + // Calculate time delta since last update auto timeNow = Time::Update.UnscaledTime; auto timeSinceUpdate = timeNow - _lastProbeUpdate; @@ -321,35 +273,32 @@ void ProbesRendererService::Update() } // Check if render job is done - if (isUpdateSynced()) + if (_updateFrameNumber > 0 && _updateFrameNumber + PROBES_RENDERER_LATENCY_FRAMES <= Engine::FrameCount) { // Create async job to gather probe data from the GPU GPUTexture* texture = nullptr; switch (_current.Type) { - case ProbesRenderer::EntryType::SkyLight: - case ProbesRenderer::EntryType::EnvProbe: + case ProbeEntry::Types::SkyLight: + case ProbeEntry::Types::EnvProbe: texture = _probe; break; } ASSERT(texture && _current.UseTextureData()); auto taskB = New(texture, _current); auto taskA = texture->DownloadDataAsync(taskB->GetData()); - if (taskA == nullptr) - { - LOG(Fatal, "Failed to create async tsk to download env probe texture data fro mthe GPU."); - } + ASSERT(taskA); taskA->ContinueWith(taskB); taskA->Start(); // Clear flag _updateFrameNumber = 0; - _current.Type = ProbesRenderer::EntryType::Invalid; + _current.Type = ProbeEntry::Types::Invalid; } - else if (_current.Type == ProbesRenderer::EntryType::Invalid) + else if (_current.Type == ProbeEntry::Types::Invalid && timeSinceUpdate > ProbesRenderer::UpdateDelay) { int32 firstValidEntryIndex = -1; - auto dt = (float)Time::Update.UnscaledDeltaTime.GetTotalSeconds(); + auto dt = Time::Update.UnscaledDeltaTime.GetTotalSeconds(); for (int32 i = 0; i < _probesToBake.Count(); i++) { auto& e = _probesToBake[i]; @@ -362,40 +311,65 @@ void ProbesRendererService::Update() } // Check if need to update probe - if (firstValidEntryIndex >= 0 && timeSinceUpdate > ProbesRenderer::ProbesUpdatedBreak) + if (firstValidEntryIndex >= 0 && timeSinceUpdate > ProbesRenderer::UpdateDelay) { - // Init service - if (ProbesRenderer::Init()) - { - LOG(Fatal, "Cannot setup Probes Renderer!"); - } - if (ProbesRenderer::HasReadyResources() == false) - return; + if (LazyInit()) + return; // Shader is not yet loaded so try the next frame // Mark probe to update _current = _probesToBake[firstValidEntryIndex]; _probesToBake.RemoveAtKeepOrder(firstValidEntryIndex); _task->Enabled = true; _updateFrameNumber = 0; - - // Store time of the last probe update _lastProbeUpdate = timeNow; } // Check if need to release data - else if (_isReady && timeSinceUpdate > ProbesRenderer::ProbesReleaseDataTime) + else if (_initDone && timeSinceUpdate > ProbesRenderer::ReleaseTimeout) { - // Release service - ProbesRenderer::Release(); + // Release resources + Dispose(); } } } void ProbesRendererService::Dispose() { - ProbesRenderer::Release(); + if (!_initDone && !_initFailed) + return; + ASSERT(_updateFrameNumber == 0); + if (_output) + _output->ReleaseGPU(); + SAFE_DELETE_GPU_RESOURCE(_psFilterFace); + SAFE_DELETE_GPU_RESOURCE(_output); + SAFE_DELETE_GPU_RESOURCE(_probe); + SAFE_DELETE_GPU_RESOURCE(_tmpFace); + SAFE_DELETE_GPU_RESOURCE(_skySHIrradianceMap); + SAFE_DELETE(_task); + _shader = nullptr; + _initDone = false; + _initFailed = false; } -bool fixFarPlaneTreeExecute(Actor* actor, const Vector3& position, float& farPlane) +void ProbesRendererService::Bake(const ProbeEntry& e) +{ + // Check if already registered for bake + for (ProbeEntry& p : _probesToBake) + { + if (p.Type == e.Type && p.Actor == e.Actor) + { + p.Timeout = e.Timeout; + return; + } + } + + _probesToBake.Add(e); + + // Fire event + if (e.UseTextureData()) + ProbesRenderer::OnRegisterBake(e.Actor); +} + +static bool FixFarPlane(Actor* actor, const Vector3& position, float& farPlane) { if (auto* pointLight = dynamic_cast(actor)) { @@ -408,20 +382,19 @@ bool fixFarPlaneTreeExecute(Actor* actor, const Vector3& position, float& farPla return true; } -void ProbesRenderer::OnRender(RenderTask* task, GPUContext* context) +void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) { - ASSERT(_current.Type != EntryType::Invalid && _updateFrameNumber == 0); switch (_current.Type) { - case EntryType::EnvProbe: - case EntryType::SkyLight: + case ProbeEntry::Types::EnvProbe: + case ProbeEntry::Types::SkyLight: { if (_current.Actor == nullptr) { // Probe has been unlinked (or deleted) _task->Enabled = false; _updateFrameNumber = 0; - _current.Type = EntryType::Invalid; + _current.Type = ProbeEntry::Types::Invalid; return; } break; @@ -430,7 +403,7 @@ void ProbesRenderer::OnRender(RenderTask* task, GPUContext* context) // Canceled return; } - + ASSERT(_updateFrameNumber == 0); auto shader = _shader->GetShader(); PROFILE_GPU("Render Probe"); @@ -438,7 +411,7 @@ void ProbesRenderer::OnRender(RenderTask* task, GPUContext* context) float customCullingNear = -1; const int32 probeResolution = _current.GetResolution(); const PixelFormat probeFormat = _current.GetFormat(); - if (_current.Type == EntryType::EnvProbe) + if (_current.Type == ProbeEntry::Types::EnvProbe) { auto envProbe = (EnvironmentProbe*)_current.Actor.Get(); Vector3 position = envProbe->GetPosition(); @@ -448,14 +421,14 @@ void ProbesRenderer::OnRender(RenderTask* task, GPUContext* context) // Adjust far plane distance float farPlane = Math::Max(radius, nearPlane + 100.0f); farPlane *= farPlane < 10000 ? 10 : 4; - Function f(&fixFarPlaneTreeExecute); + Function f(&FixFarPlane); SceneQuery::TreeExecute(f, position, farPlane); // Setup view LargeWorlds::UpdateOrigin(_task->View.Origin, position); _task->View.SetUpCube(nearPlane, farPlane, position - _task->View.Origin); } - else if (_current.Type == EntryType::SkyLight) + else if (_current.Type == ProbeEntry::Types::SkyLight) { auto skyLight = (SkyLight*)_current.Actor.Get(); Vector3 position = skyLight->GetPosition(); @@ -481,6 +454,9 @@ void ProbesRenderer::OnRender(RenderTask* task, GPUContext* context) const bool isActorActive = _current.Actor->GetIsActive(); _current.Actor->SetIsActive(false); + // Lower quality when rendering probes in-game to gain performance + _task->View.MaxShadowsQuality = Engine::IsPlayMode() ? Quality::Low : Quality::Ultra; + // Render scene for all faces for (int32 faceIndex = 0; faceIndex < 6; faceIndex++) { @@ -556,13 +532,13 @@ void ProbesRenderer::OnRender(RenderTask* task, GPUContext* context) // Real-time probes don't use TextureData (for streaming) but copy generated probe directly to GPU memory if (!_current.UseTextureData()) { - if (_current.Type == EntryType::EnvProbe && _current.Actor) + if (_current.Type == ProbeEntry::Types::EnvProbe && _current.Actor) { _current.Actor.As()->SetProbeData(context, _probe); } // Clear flag _updateFrameNumber = 0; - _current.Type = EntryType::Invalid; + _current.Type = ProbeEntry::Types::Invalid; } } diff --git a/Source/Engine/Renderer/ProbesRenderer.h b/Source/Engine/Renderer/ProbesRenderer.h index 5c4e011e4..0e2007a37 100644 --- a/Source/Engine/Renderer/ProbesRenderer.h +++ b/Source/Engine/Renderer/ProbesRenderer.h @@ -2,75 +2,30 @@ #pragma once -#include "Engine/Graphics/PixelFormat.h" -#include "Engine/Scripting/ScriptingObjectReference.h" -#include "Engine/Level/Actor.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Types/TimeSpan.h" -// Amount of frames to wait for data from probe update job -#define PROBES_RENDERER_LATENCY_FRAMES 1 - -class EnvironmentProbe; -class SkyLight; -class RenderTask; +class Actor; /// /// Probes rendering service /// class ProbesRenderer { -public: - enum class EntryType - { - Invalid = 0, - EnvProbe = 1, - SkyLight = 2, - }; - - struct Entry - { - EntryType Type = EntryType::Invalid; - ScriptingObjectReference Actor; - float Timeout = 0.0f; - - bool UseTextureData() const; - int32 GetResolution() const; - PixelFormat GetFormat() const; - }; - public: /// - /// Minimum amount of time between two updated of probes + /// Time delay between probe updates. Can be used to improve performance by rendering probes less often. /// - static TimeSpan ProbesUpdatedBreak; + static TimeSpan UpdateDelay; /// - /// Time after last probe update when probes updating content will be released + /// Timeout after the last probe rendered when resources used to render it should be released. /// - static TimeSpan ProbesReleaseDataTime; + static TimeSpan ReleaseTimeout; - int32 GetBakeQueueSize(); + static Delegate OnRegisterBake; - static Delegate OnRegisterBake; - - static Delegate OnFinishBake; - -public: - /// - /// Checks if resources are ready to render probes (shaders or textures may be during loading). - /// - /// True if is ready, otherwise false. - static bool HasReadyResources(); - - /// - /// Init probes content - /// - /// True if cannot init service - static bool Init(); - - /// - /// Release probes content - /// - static void Release(); + static Delegate OnFinishBake; public: /// @@ -78,15 +33,12 @@ public: /// /// Probe to bake /// Timeout in seconds left to bake it. - static void Bake(EnvironmentProbe* probe, float timeout = 0); + static void Bake(class EnvironmentProbe* probe, float timeout = 0); /// /// Register probe to baking service. /// /// Probe to bake /// Timeout in seconds left to bake it. - static void Bake(SkyLight* probe, float timeout = 0); - -private: - static void OnRender(RenderTask* task, GPUContext* context); + static void Bake(class SkyLight* probe, float timeout = 0); }; From 33e58c12cbea2acd04e4de45fcb74fed89cd2241 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Jul 2025 11:43:56 +0200 Subject: [PATCH 76/84] Optimize `ProbesRenderer` to use time-slicing for cubemap faces rendering and filtering --- Source/Engine/Renderer/ProbesRenderer.cpp | 101 +++++++++++++--------- Source/Engine/Renderer/ProbesRenderer.h | 5 ++ 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index ac19cd309..ae94385e2 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -106,6 +106,8 @@ private: Array _probesToBake; ProbeEntry _current; + int32 _workStep; + float _customCullingNear; AssetReference _shader; GPUPipelineState* _psFilterFace = nullptr; @@ -134,6 +136,7 @@ ProbesRendererService ProbesRendererServiceInstance; TimeSpan ProbesRenderer::UpdateDelay(0, 0, 0, 0, 100); TimeSpan ProbesRenderer::ReleaseTimeout(0, 0, 0, 30); +int32 ProbesRenderer::MaxWorkPerFrame = 1; Delegate ProbesRenderer::OnRegisterBake; Delegate ProbesRenderer::OnFinishBake; @@ -293,6 +296,7 @@ void ProbesRendererService::Update() // Clear flag _updateFrameNumber = 0; + _workStep = 0; _current.Type = ProbeEntry::Types::Invalid; } else if (_current.Type == ProbeEntry::Types::Invalid && timeSinceUpdate > ProbesRenderer::UpdateDelay) @@ -321,6 +325,7 @@ void ProbesRendererService::Update() _probesToBake.RemoveAtKeepOrder(firstValidEntryIndex); _task->Enabled = true; _updateFrameNumber = 0; + _workStep = 0; _lastProbeUpdate = timeNow; } // Check if need to release data @@ -408,72 +413,76 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) PROFILE_GPU("Render Probe"); // Init - float customCullingNear = -1; const int32 probeResolution = _current.GetResolution(); const PixelFormat probeFormat = _current.GetFormat(); - if (_current.Type == ProbeEntry::Types::EnvProbe) + if (_workStep == 0) { - auto envProbe = (EnvironmentProbe*)_current.Actor.Get(); - Vector3 position = envProbe->GetPosition(); - float radius = envProbe->GetScaledRadius(); - float nearPlane = Math::Max(0.1f, envProbe->CaptureNearPlane); + _customCullingNear = -1; + if (_current.Type == ProbeEntry::Types::EnvProbe) + { + auto envProbe = (EnvironmentProbe*)_current.Actor.Get(); + Vector3 position = envProbe->GetPosition(); + float radius = envProbe->GetScaledRadius(); + float nearPlane = Math::Max(0.1f, envProbe->CaptureNearPlane); - // Adjust far plane distance - float farPlane = Math::Max(radius, nearPlane + 100.0f); - farPlane *= farPlane < 10000 ? 10 : 4; - Function f(&FixFarPlane); - SceneQuery::TreeExecute(f, position, farPlane); + // Adjust far plane distance + float farPlane = Math::Max(radius, nearPlane + 100.0f); + farPlane *= farPlane < 10000 ? 10 : 4; + Function f(&FixFarPlane); + SceneQuery::TreeExecute(f, position, farPlane); - // Setup view - LargeWorlds::UpdateOrigin(_task->View.Origin, position); - _task->View.SetUpCube(nearPlane, farPlane, position - _task->View.Origin); + // Setup view + LargeWorlds::UpdateOrigin(_task->View.Origin, position); + _task->View.SetUpCube(nearPlane, farPlane, position - _task->View.Origin); + } + else if (_current.Type == ProbeEntry::Types::SkyLight) + { + auto skyLight = (SkyLight*)_current.Actor.Get(); + Vector3 position = skyLight->GetPosition(); + float nearPlane = 10.0f; + float farPlane = Math::Max(nearPlane + 1000.0f, skyLight->SkyDistanceThreshold * 2.0f); + _customCullingNear = skyLight->SkyDistanceThreshold; + + // Setup view + LargeWorlds::UpdateOrigin(_task->View.Origin, position); + _task->View.SetUpCube(nearPlane, farPlane, position - _task->View.Origin); + } + + // Resize buffers + bool resizeFailed = _output->Resize(probeResolution, probeResolution, probeFormat); + resizeFailed |= _probe->Resize(probeResolution, probeResolution, probeFormat); + resizeFailed |= _tmpFace->Resize(probeResolution, probeResolution, probeFormat); + resizeFailed |= _task->Resize(probeResolution, probeResolution); + if (resizeFailed) + LOG(Error, "Failed to resize probe"); } - else if (_current.Type == ProbeEntry::Types::SkyLight) - { - auto skyLight = (SkyLight*)_current.Actor.Get(); - Vector3 position = skyLight->GetPosition(); - float nearPlane = 10.0f; - float farPlane = Math::Max(nearPlane + 1000.0f, skyLight->SkyDistanceThreshold * 2.0f); - customCullingNear = skyLight->SkyDistanceThreshold; - - // Setup view - LargeWorlds::UpdateOrigin(_task->View.Origin, position); - _task->View.SetUpCube(nearPlane, farPlane, position - _task->View.Origin); - } - _task->CameraCut(); - - // Resize buffers - bool resizeFailed = _output->Resize(probeResolution, probeResolution, probeFormat); - resizeFailed |= _probe->Resize(probeResolution, probeResolution, probeFormat); - resizeFailed |= _tmpFace->Resize(probeResolution, probeResolution, probeFormat); - resizeFailed |= _task->Resize(probeResolution, probeResolution); - if (resizeFailed) - LOG(Error, "Failed to resize probe"); // Disable actor during baking (it cannot influence own results) const bool isActorActive = _current.Actor->GetIsActive(); _current.Actor->SetIsActive(false); // Lower quality when rendering probes in-game to gain performance - _task->View.MaxShadowsQuality = Engine::IsPlayMode() ? Quality::Low : Quality::Ultra; + _task->View.MaxShadowsQuality = Engine::IsPlayMode() || probeResolution <= 128 ? Quality::Low : Quality::Ultra; // Render scene for all faces - for (int32 faceIndex = 0; faceIndex < 6; faceIndex++) + int32 workLeft = ProbesRenderer::MaxWorkPerFrame; + const int32 lastFace = Math::Min(_workStep + workLeft, 6); + for (int32 faceIndex = _workStep; faceIndex < lastFace; faceIndex++) { + _task->CameraCut(); _task->View.SetFace(faceIndex); // Handle custom frustum for the culling (used to skip objects near the camera) - if (customCullingNear > 0) + if (_customCullingNear > 0) { Matrix p; - Matrix::PerspectiveFov(PI_OVER_2, 1.0f, customCullingNear, _task->View.Far, p); + Matrix::PerspectiveFov(PI_OVER_2, 1.0f, _customCullingNear, _task->View.Far, p); _task->View.CullingFrustum.SetMatrix(_task->View.View, p); } // Render frame Renderer::Render(_task); context->ClearState(); - _task->CameraCut(); // Copy frame to cube face { @@ -483,12 +492,17 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) context->Draw(_output->View()); context->ResetRenderTarget(); } + + // Move to the next face + _workStep++; + workLeft--; } // Enable actor back _current.Actor->SetIsActive(isActorActive); // Filter all lower mip levels + if (workLeft > 0) { PROFILE_GPU("Filtering"); Data data; @@ -520,11 +534,18 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) context->Draw(_tmpFace->View(0, mipIndex)); } } + + // End + workLeft--; + _workStep++; } // Cleanup context->ClearState(); + if (_workStep < 7) + return; // Continue rendering next frame + // Mark as rendered _updateFrameNumber = Engine::FrameCount; _task->Enabled = false; diff --git a/Source/Engine/Renderer/ProbesRenderer.h b/Source/Engine/Renderer/ProbesRenderer.h index 0e2007a37..73d8b4132 100644 --- a/Source/Engine/Renderer/ProbesRenderer.h +++ b/Source/Engine/Renderer/ProbesRenderer.h @@ -23,6 +23,11 @@ public: /// static TimeSpan ReleaseTimeout; + /// + /// Maximum amount of cubemap faces or filtering passes that can be performed per-frame (in total). Set it to 7 to perform whole cubemap capture within a single frame, lower values spread the work across multiple frames. + /// + static int32 MaxWorkPerFrame; + static Delegate OnRegisterBake; static Delegate OnFinishBake; From a138c6b062bf1a0fbf3610add18a6f7f581c5ac7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Jul 2025 11:45:12 +0200 Subject: [PATCH 77/84] Optimize environment probes filtering shader --- Content/Shaders/ProbesFilter.flax | 4 +- Source/Engine/Renderer/ProbesRenderer.cpp | 48 +++++++++++++++++------ Source/Shaders/ProbesFilter.shader | 8 ++-- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/Content/Shaders/ProbesFilter.flax b/Content/Shaders/ProbesFilter.flax index 0f853c5b4..679eac27b 100644 --- a/Content/Shaders/ProbesFilter.flax +++ b/Content/Shaders/ProbesFilter.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0249696b525cd59825ab3c0ce38bd612f93cf4be1f88fb49bfcecaac6e9ab34 -size 2022 +oid sha256:bbe90799accc93fabdc900df37bf762132037eeaff17f5731f379b6b3d017d2b +size 2033 diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index ae94385e2..9eb5f3937 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -125,11 +125,21 @@ public: } bool LazyInit(); + bool InitShader(); void Update() override; void Dispose() override; - void Bake(const ProbeEntry& e); + +private: void OnRender(RenderTask* task, GPUContext* context); +#if COMPILE_WITH_DEV_ENV + bool _initShader = false; + void OnShaderReloading(Asset* obj) + { + _initShader = true; + SAFE_DELETE_GPU_RESOURCE(_psFilterFace); + } +#endif }; ProbesRendererService ProbesRendererServiceInstance; @@ -206,19 +216,13 @@ bool ProbesRendererService::LazyInit() _initFailed = _shader == nullptr; if (_initFailed) return false; +#if COMPILE_WITH_DEV_ENV + _shader->OnReloading.Bind(this); +#endif } if (!_shader->IsLoaded()) return true; - const auto shader = _shader->GetShader(); - CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); - - // Create pipeline stages - _psFilterFace = GPUDevice::Instance->CreatePipelineState(); - auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; - { - psDesc.PS = shader->GetPS("PS_FilterFace"); - _initFailed |= _psFilterFace->Init(psDesc); - } + _initFailed |= InitShader(); // Init rendering pipeline _output = GPUDevice::Instance->CreateTexture(TEXT("ProbesRenderer.Output")); @@ -262,6 +266,16 @@ bool ProbesRendererService::LazyInit() return false; } +bool ProbesRendererService::InitShader() +{ + const auto shader = _shader->GetShader(); + CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); + _psFilterFace = GPUDevice::Instance->CreatePipelineState(); + auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + psDesc.PS = shader->GetPS("PS_FilterFace"); + return _psFilterFace->Init(psDesc); +} + void ProbesRendererService::Update() { PROFILE_MEM(Graphics); @@ -412,6 +426,18 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) auto shader = _shader->GetShader(); PROFILE_GPU("Render Probe"); +#if COMPILE_WITH_DEV_ENV + // handle shader hot-reload + if (_initShader) + { + if (_shader->WaitForLoaded()) + return; + _initShader = false; + if (InitShader()) + return; + } +#endif + // Init const int32 probeResolution = _current.GetResolution(); const PixelFormat probeFormat = _current.GetFormat(); diff --git a/Source/Shaders/ProbesFilter.shader b/Source/Shaders/ProbesFilter.shader index 437d484f5..c64a281f2 100644 --- a/Source/Shaders/ProbesFilter.shader +++ b/Source/Shaders/ProbesFilter.shader @@ -50,18 +50,16 @@ float4 PS_FilterFace(Quad_VS2PS input) : SV_Target float2 uv = input.TexCoord * 2 - 1; float3 cubeCoordinates = UvToCubeMapUv(uv); -#define NUM_FILTER_SAMPLES 512 - float3 N = normalize(cubeCoordinates); float roughness = ProbeRoughnessFromMip(SourceMipIndex); + const uint samplesCount = roughness > 0.1 ? 64 : 32; float4 filteredColor = 0; float weight = 0; - LOOP - for (int i = 0; i < NUM_FILTER_SAMPLES; i++) + for (int i = 0; i < samplesCount; i++) { - float2 E = Hammersley(i, NUM_FILTER_SAMPLES, 0); + float2 E = Hammersley(i, samplesCount, 0); float3 H = TangentToWorld(ImportanceSampleGGX(E, roughness).xyz, N); float3 L = 2 * dot(N, H) * H - N; float NoL = saturate(dot(N, L)); From bf345f13ce573cc8a7aaffe30aa8fae222d1e19a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Jul 2025 13:54:22 +0200 Subject: [PATCH 78/84] Fix reflection probes capture seams on cube face edges due to volumetric fog #3252 --- Source/Engine/Graphics/RenderTask.cpp | 9 ++++++++- Source/Engine/Renderer/ProbesRenderer.cpp | 13 ++++++++++--- Source/Engine/Renderer/RenderSetup.h | 1 + Source/Engine/Renderer/Renderer.cpp | 1 + Source/Engine/Renderer/VolumetricFogPass.cpp | 5 ++--- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index ecdcd572c..ac969ad2d 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -200,12 +200,19 @@ void SceneRenderTask::RemoveGlobalCustomPostFx(PostProcessEffect* fx) void SceneRenderTask::CollectPostFxVolumes(RenderContext& renderContext) { + PROFILE_CPU(); + // Cache WorldPosition used for PostFx volumes blending (RenderView caches it later on) renderContext.View.WorldPosition = renderContext.View.Origin + renderContext.View.Position; if (EnumHasAllFlags(ActorsSource, ActorsSources::Scenes)) { - Level::CollectPostFxVolumes(renderContext); + //ScopeLock lock(Level::ScenesLock); + for (Scene* scene : Level::Scenes) + { + if (scene->IsActiveInHierarchy()) + scene->Rendering.CollectPostFxVolumes(renderContext); + } } if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomActors)) { diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 9eb5f3937..eaf7a53ca 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -2,6 +2,7 @@ #include "ProbesRenderer.h" #include "Renderer.h" +#include "RenderList.h" #include "ReflectionsPass.h" #include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Engine/Time.h" @@ -17,7 +18,6 @@ #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/AssetReference.h" -#include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/PixelFormat.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/Textures/GPUTexture.h" @@ -115,7 +115,6 @@ private: GPUTexture* _output = nullptr; GPUTexture* _probe = nullptr; GPUTexture* _tmpFace = nullptr; - GPUTexture* _skySHIrradianceMap = nullptr; uint64 _updateFrameNumber = 0; public: @@ -132,6 +131,7 @@ public: private: void OnRender(RenderTask* task, GPUContext* context); + void OnSetupRender(RenderContext& renderContext); #if COMPILE_WITH_DEV_ENV bool _initShader = false; void OnShaderReloading(Asset* obj) @@ -234,6 +234,7 @@ bool ProbesRendererService::LazyInit() task->Order = -100; // Run before main view rendering (realtime probes will get smaller latency) task->Enabled = false; task->IsCustomRendering = true; + task->ActorsSource = ActorsSources::ScenesAndCustomActors; task->Output = _output; auto& view = task->View; view.Flags = @@ -254,6 +255,7 @@ bool ProbesRendererService::LazyInit() task->IsCameraCut = true; task->Resize(probeResolution, probeResolution); task->Render.Bind(this); + task->SetupRender.Bind(this); // Init render targets _probe = GPUDevice::Instance->CreateTexture(TEXT("ProbesRenderer.Probe")); @@ -362,7 +364,6 @@ void ProbesRendererService::Dispose() SAFE_DELETE_GPU_RESOURCE(_output); SAFE_DELETE_GPU_RESOURCE(_probe); SAFE_DELETE_GPU_RESOURCE(_tmpFace); - SAFE_DELETE_GPU_RESOURCE(_skySHIrradianceMap); SAFE_DELETE(_task); _shader = nullptr; _initDone = false; @@ -589,3 +590,9 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) _current.Type = ProbeEntry::Types::Invalid; } } + +void ProbesRendererService::OnSetupRender(RenderContext& renderContext) +{ + // Disable Volumetric Fog in reflection as it causes seams on cubemap face edges + renderContext.List->Setup.UseVolumetricFog = false; +} diff --git a/Source/Engine/Renderer/RenderSetup.h b/Source/Engine/Renderer/RenderSetup.h index 10377e023..3444f0838 100644 --- a/Source/Engine/Renderer/RenderSetup.h +++ b/Source/Engine/Renderer/RenderSetup.h @@ -14,4 +14,5 @@ struct FLAXENGINE_API RenderSetup bool UseTemporalAAJitter = false; bool UseGlobalSDF = false; bool UseGlobalSurfaceAtlas = false; + bool UseVolumetricFog = false; }; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 87d1d94f1..fd02b133f 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -379,6 +379,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont setup.UseGlobalSDF = (graphicsSettings->EnableGlobalSDF && EnumHasAnyFlags(view.Flags, ViewFlags::GlobalSDF)) || renderContext.View.Mode == ViewMode::GlobalSDF || setup.UseGlobalSurfaceAtlas; + setup.UseVolumetricFog = (view.Flags & ViewFlags::Fog) != ViewFlags::None; // Disable TAA jitter in debug modes switch (renderContext.View.Mode) diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index b7e57c2bb..6029b399d 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -99,7 +99,6 @@ float ComputeZSliceFromDepth(float sceneDepth, const VolumetricFogOptions& optio bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) { - auto& view = renderContext.View; const auto fog = renderContext.List->Fog; // Check if already prepared for this frame @@ -111,7 +110,7 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, } // Check if skip rendering - if (fog == nullptr || (view.Flags & ViewFlags::Fog) == ViewFlags::None || !_isSupported || checkIfSkipPass()) + if (fog == nullptr || !renderContext.List->Setup.UseVolumetricFog || !_isSupported || checkIfSkipPass()) { RenderTargetPool::Release(renderContext.Buffers->VolumetricFog); renderContext.Buffers->VolumetricFog = nullptr; @@ -184,7 +183,7 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, _cache.Data.PhaseG = options.ScatteringDistribution; _cache.Data.VolumetricFogMaxDistance = options.Distance; _cache.Data.MissedHistorySamplesCount = Math::Clamp(_cache.MissedHistorySamplesCount, 1, (int32)ARRAY_COUNT(_cache.Data.FrameJitterOffsets)); - Matrix::Transpose(view.PrevViewProjection, _cache.Data.PrevWorldToClip); + Matrix::Transpose(renderContext.View.PrevViewProjection, _cache.Data.PrevWorldToClip); _cache.Data.SkyLight.VolumetricScatteringIntensity = 0; // Fill frame jitter history From da08be42b4d73307eb4a25fc3fb3fecf282636aa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Jul 2025 20:32:12 +0200 Subject: [PATCH 79/84] Fix deadlock in Debug builds on object dtor --- Source/Engine/Core/ObjectsRemovalService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index 4d9159ea9..052bd6040 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -154,7 +154,7 @@ void ObjectsRemoval::Dispose() Object::~Object() { -#if BUILD_DEBUG +#if BUILD_DEBUG && 0 // Prevent removing object that is still reverenced by the removal service ASSERT(!ObjectsRemovalService::IsInPool(this)); #endif From 85aed8c4d70f37e63b536aa88ac0252e01132525 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Jul 2025 20:32:41 +0200 Subject: [PATCH 80/84] Fix using material VS to PS node directly within material input --- .../Tools/MaterialGenerator/MaterialGenerator.Material.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index 0617385bc..9105fcd2e 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -10,6 +10,10 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) { switch (node->TypeID) { + // Material + case 1: + value = tryGetValue(box, Value::Zero); + break; // World Position case 2: value = Value(VariantType::Float3, TEXT("input.WorldPosition.xyz")); From 4b10d7057aad73fdc39db71029c6d7d54878085a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Jul 2025 20:33:14 +0200 Subject: [PATCH 81/84] Fix crash when using material instance that has more parameters that base due to material error --- Source/Engine/Graphics/Materials/MaterialParams.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index e31697f77..d670b188a 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -604,10 +604,11 @@ int32 MaterialParams::GetVersionHash() const void MaterialParams::Bind(MaterialParamsLink* link, MaterialParameter::BindMeta& meta) { ASSERT(link && link->This); - for (int32 i = 0; i < link->This->Count(); i++) + const int32 count = link->This->Count(); + for (int32 i = 0; i < count; i++) { MaterialParamsLink* l = link; - while (l->Down && !l->This->At(i).IsOverride()) + while (l->Down && !l->This->At(i).IsOverride() && l->Down->This->Count() == count) { l = l->Down; } From 7abed939729a701381c508fe2970c30e676a15a3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Jul 2025 11:31:27 +0200 Subject: [PATCH 82/84] Optimize terrain heightmap decoding to use shared code --- .../Editor/MaterialTemplates/Terrain.shader | 12 +++--- Source/Engine/Visject/ShaderGraph.cpp | 2 +- Source/Shaders/TerrainCommon.hlsl | 37 +++++++++---------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index abc444316..63313e304 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -15,6 +15,7 @@ #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" +#include "./Flax/TerrainCommon.hlsl" @7 // Primary constant buffer (with additional material parameters) META_CB_BEGIN(0, Data) @@ -334,7 +335,7 @@ VertexOutput VS(TerrainVertexInput input) float lodValue = CurrentLOD; float morphAlpha = lodCalculated - CurrentLOD; - // Sample heightmap + // Sample heightmap and splatmaps float2 heightmapUVs = input.TexCoord * HeightmapUVScaleBias.xy + HeightmapUVScaleBias.zw; #if USE_SMOOTH_LOD_TRANSITION float4 heightmapValueThisLOD = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue); @@ -342,7 +343,6 @@ VertexOutput VS(TerrainVertexInput input) float2 heightmapUVsNextLOD = nextLODPos * HeightmapUVScaleBias.xy + HeightmapUVScaleBias.zw; float4 heightmapValueNextLOD = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVsNextLOD, lodValue + 1); float4 heightmapValue = lerp(heightmapValueThisLOD, heightmapValueNextLOD, morphAlpha); - bool isHole = max(heightmapValueThisLOD.b + heightmapValueThisLOD.a, heightmapValueNextLOD.b + heightmapValueNextLOD.a) >= 1.9f; #if USE_TERRAIN_LAYERS float4 splatmapValueThisLOD = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue); float4 splatmapValueNextLOD = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVsNextLOD, lodValue + 1); @@ -355,7 +355,6 @@ VertexOutput VS(TerrainVertexInput input) #endif #else float4 heightmapValue = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue); - bool isHole = (heightmapValue.b + heightmapValue.a) >= 1.9f; #if USE_TERRAIN_LAYERS float4 splatmap0Value = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue); #if TERRAIN_LAYERS_DATA_SIZE > 1 @@ -363,12 +362,11 @@ VertexOutput VS(TerrainVertexInput input) #endif #endif #endif - float height = (float)((int)(heightmapValue.x * 255.0) + ((int)(heightmapValue.y * 255) << 8)) / 65535.0; + float height = DecodeHeightmapHeight(heightmapValue); // Extract normal and the holes mask - float2 normalTemp = float2(heightmapValue.b, heightmapValue.a) * 2.0f - 1.0f; - float3 normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); - normal = normalize(normal); + bool isHole; + float3 normal = DecodeHeightmapNormal(heightmapValue, isHole); output.Geometry.HolesMask = isHole ? 0 : 1; if (isHole) { diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index ef4f53cf9..b6616d159 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -286,7 +286,7 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) case 29: { Value inXY = tryGetValue(node->GetBox(0), Value::Zero).AsFloat2(); - value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, sqrt(saturate(1.0 - dot({0}.xy, {0}.xy))))"), inXY.Value), node); + value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, sqrt(saturate(1.0 - dot({0}, {0}))))"), inXY.Value), node); break; } // Mad diff --git a/Source/Shaders/TerrainCommon.hlsl b/Source/Shaders/TerrainCommon.hlsl index a4db9bd4f..0c2f57168 100644 --- a/Source/Shaders/TerrainCommon.hlsl +++ b/Source/Shaders/TerrainCommon.hlsl @@ -5,28 +5,30 @@ #include "./Flax/Common.hlsl" +float DecodeHeightmapHeight(float4 value) +{ + return (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0; +} + +float3 DecodeHeightmapNormal(float4 value, out bool isHole) +{ + isHole = (value.b + value.a) >= 1.9f; + float2 normalTemp = float2(value.b, value.a) * 2.0f - 1.0f; + float3 normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); + return normalize(normal); +} + float SampleHeightmap(Texture2D heightmap, float2 uv, float mipOffset = 0.0f) { - // Sample heightmap float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset); - - // Decode heightmap - float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0; - return height; + return DecodeHeightmapHeight(value); } float SampleHeightmap(Texture2D heightmap, float2 uv, out float3 normal, out bool isHole, float mipOffset = 0.0f) { - // Sample heightmap float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset); - - // Decode heightmap - float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0; - float2 normalTemp = float2(value.b, value.a) * 2.0f - 1.0f; - normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); - isHole = (value.b + value.a) >= 1.9f; - normal = normalize(normal); - return height; + normal = DecodeHeightmapNormal(value, isHole); + return DecodeHeightmapHeight(value); } float3 SampleHeightmap(Texture2D heightmap, float3 localPosition, float4 localToUV, out float3 normal, out bool isHole, float mipOffset = 0.0f) @@ -36,12 +38,9 @@ float3 SampleHeightmap(Texture2D heightmap, float3 localPosition, float4 float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset); // Decode heightmap - isHole = (value.b + value.a) >= 1.9f; - float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0; + normal = DecodeHeightmapNormal(value, isHole); + float height = DecodeHeightmapHeight(value);; float3 position = float3(localPosition.x, height, localPosition.z); - float2 normalTemp = float2(value.b, value.a) * 2.0f - 1.0f; - normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); - normal = normalize(normal); // UVs outside the heightmap are empty isHole = isHole || any(uv < 0.0f) || any(uv > 1.0f); From 0bc595f16fb3a3dc7076aa0b4af217895ac7e57e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Jul 2025 12:04:36 +0200 Subject: [PATCH 83/84] Disable terarin normals smoothening --- .../Editor/SceneGraph/Actors/TerrainNode.cs | 4 ++++ Source/Engine/Terrain/TerrainPatch.cpp | 20 ++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/TerrainNode.cs b/Source/Editor/SceneGraph/Actors/TerrainNode.cs index f5cef604d..4e2cd3346 100644 --- a/Source/Editor/SceneGraph/Actors/TerrainNode.cs +++ b/Source/Editor/SceneGraph/Actors/TerrainNode.cs @@ -76,9 +76,13 @@ namespace FlaxEditor.SceneGraph.Actors // Skip removing this terrain file sif it's still referenced var sceneReferences = Editor.GetAssetReferences(e.SceneId); if (sceneReferences != null && sceneReferences.Contains(e.TerrainId)) + { + Debug.Log($"Skip removing files used by terrain {e.TerrainId} on scene {e.SceneId} as it's still in use"); continue; + } // Delete files + Debug.Log($"Removing files used by removed terrain {e.TerrainId} on scene {e.SceneId}"); foreach (var file in e.Files) { if (file != null && File.Exists(file)) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 1c754d843..a9388224d 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -431,8 +431,6 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh GET_VERTEX(1, 1); #undef GET_VERTEX - // TODO: use SIMD for those calculations - // Calculate normals for quad two vertices Float3 n0 = Float3::Normalize((v00 - v01) ^ (v01 - v10)); Float3 n1 = Float3::Normalize((v11 - v10) ^ (v10 - v01)); @@ -446,6 +444,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh } } +#if 0 // Smooth normals for (int32 z = 1; z < normalsSize.Y - 1; z++) { @@ -466,8 +465,6 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh GET_NORMAL(2, 2); #undef GET_VERTEX - // TODO: use SIMD for those calculations - /* * The current vertex is (11). Calculate average for the nearby vertices. * 00 01 02 @@ -481,6 +478,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh normalsPerVertex[i11] = Float3::Lerp(n11, avg, 0.6f); } } +#endif // Write back to the data container const auto ptr = (Color32*)data; @@ -525,10 +523,9 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh const int32 textureIndex = tz + tx; const int32 heightmapIndex = hz + hx; const int32 normalIndex = sz + sx; -#if BUILD_DEBUG - ASSERT(normalIndex >= 0 && normalIndex < normalsLength); -#endif - Float3 normal = Float3::NormalizeFast(normalsPerVertex[normalIndex]) * 0.5f + 0.5f; + ASSERT_LOW_LAYER(normalIndex >= 0 && normalIndex < normalsLength); + Float3 normal = Float3::NormalizeFast(normalsPerVertex[normalIndex]); + normal = normal * 0.5f + 0.5f; if (holesMask && !holesMask[heightmapIndex]) normal = Float3::One; @@ -1247,6 +1244,11 @@ void TerrainPatch::ClearCache() void TerrainPatch::CacheHeightData() { + if (Heightmap == nullptr) + { + LOG(Error, "Missing heightmap."); + return; + } PROFILE_CPU_NAMED("Terrain.CacheHeightData"); const TerrainDataUpdateInfo info(this); @@ -1745,7 +1747,7 @@ bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& mod // Prepare data for the uploading to GPU ASSERT(Heightmap); auto texture = Heightmap->GetTexture(); - ASSERT(texture->ResidentMipLevels() > 0); + ASSERT(texture->IsAllocated()); const int32 textureSize = texture->Width(); const PixelFormat pixelFormat = texture->Format(); const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat); From 48c6339ebbbcb699a457c7ba5a3b87b9c8df03ba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Jul 2025 12:21:25 +0200 Subject: [PATCH 84/84] Fix memory leak on material instances when updating layout of Text Render --- Source/Engine/UI/TextRender.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 951da2316..6b2988b7a 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -190,6 +190,10 @@ void TextRender::UpdateLayout() _buffersDirty = true; // Init draw chunks data + Array> materials; + materials.Resize(_drawChunks.Count()); + for (int32 i = 0; i < materials.Count(); i++) + materials[i] = _drawChunks[i].Material; DrawChunk drawChunk; drawChunk.Actor = this; drawChunk.StartIndex = 0; @@ -242,10 +246,12 @@ void TextRender::UpdateLayout() } // Setup material - drawChunk.Material = Content::CreateVirtualAsset(); + if (_drawChunks.Count() < materials.Count()) + drawChunk.Material = materials[_drawChunks.Count()]; + else + drawChunk.Material = Content::CreateVirtualAsset(); drawChunk.Material->SetBaseMaterial(Material.Get()); - for (auto& param : drawChunk.Material->Params) - param.SetIsOverride(false); + drawChunk.Material->ResetParameters(); // Set the font parameter static StringView FontParamName = TEXT("Font");