diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 22d9ca9b4..657d990e4 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/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/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/Flax.flaxproj b/Flax.flaxproj index 450cbe00a..aa685fd07 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.", 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/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/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/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/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/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. diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index d65ae6e0a..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); } @@ -156,7 +157,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 +175,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/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/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/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/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/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 0fe01ac6e..0d77c4cdd 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" @@ -77,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() { @@ -276,7 +277,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) @@ -417,6 +418,7 @@ void ScriptsBuilder::GetBinariesConfiguration(const Char*& target, const Char*& bool ScriptsBuilderImpl::compileGameScriptsAsyncInner() { + PROFILE_MEM(Editor); LOG(Info, "Starting scripts compilation..."); CallEvent(EventType::CompileStarted); @@ -523,6 +525,8 @@ void ScriptsBuilderImpl::onEditorAssemblyUnloading(MAssembly* assembly) bool ScriptsBuilderImpl::compileGameScriptsAsync() { + PROFILE_MEM(Editor); + // Start { ScopeLock scopeLock(_locker); @@ -566,6 +570,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 @@ -663,6 +668,9 @@ bool ScriptsBuilderService::Init() void ScriptsBuilderService::Update() { + PROFILE_CPU(); + PROFILE_MEM(Editor); + // Send compilation events { ScopeLock scopeLock(_compileEventsLocker); diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index e46b1c6fb..e46038639 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -6,6 +6,7 @@ using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Windows.Assets; using FlaxEngine; +using FlaxEngine.GUI; namespace FlaxEditor.Surface.Archetypes { @@ -123,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; @@ -134,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; @@ -260,6 +262,211 @@ 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 + + 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) + { + 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) + { + return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location); + } + + public override void OnSurfaceLoaded(SurfaceNodeActions action) + { + base.OnSurfaceLoaded(action); + + _textBox.Text = (string)Values[0]; + + 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); + _textBox.Text = (string)Values[0]; + } + + 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 +617,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[] { @@ -433,8 +642,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 @@ -874,6 +1081,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 +1091,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[] { @@ -890,7 +1099,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/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 1f721d289..59c980156 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" @@ -263,6 +264,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/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/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index d7bdc43af..a74472a8c 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,22 @@ 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; + private Label _warningText; + public Memory() : base("Memory") { @@ -50,6 +65,70 @@ namespace FlaxEditor.Windows.Profiler Parent = layout, }; _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; + 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 +136,7 @@ namespace FlaxEditor.Windows.Profiler { _nativeAllocationsChart.Clear(); _managedAllocationsChart.Clear(); + _frames?.Clear(); } /// @@ -84,6 +164,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 +184,112 @@ 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; + 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]; + 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 tmp.Usage.Values0[y].CompareTo(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/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs index acaeca364..74f14b584 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,13 +139,15 @@ 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]; + if (!gpuResource) + continue; // Try to reuse cached resource info var gpuResourceId = gpuResource.ID; @@ -219,6 +222,7 @@ namespace FlaxEditor.Windows.Profiler if (_resources == null) _resources = new SamplesBuffer(); _resources.Add(resources); + Array.Clear(_gpuResourcesCached); } /// @@ -255,6 +259,7 @@ namespace FlaxEditor.Windows.Profiler _assetPathToId?.Clear(); _tableRowsCache?.Clear(); _stringBuilder?.Clear(); + _gpuResourcesCached = null; base.OnDestroy(); } diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp index e70ca50a4..442051d73 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; @@ -70,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() @@ -172,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/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/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/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..2ca7f3512 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" @@ -18,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; @@ -318,6 +320,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/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/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/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/Asset.cpp b/Source/Engine/Content/Asset.cpp index 4130993eb..559a673da 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,39 @@ 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 + ScopeLock lock(_referencesLocker); + _references.Add(ref); + } +} + +void Asset::RemoveReference() +{ + Platform::InterlockedDecrement(&_refCount); +} + +void Asset::RemoveReference(IAssetReference* ref, bool week) +{ + if (ref) + { + ScopeLock lock(_referencesLocker); + _references.Remove(ref); + } + if (!week) + Platform::InterlockedDecrement(&_refCount); +} + String Asset::ToString() const { return String::Format(TEXT("{0}, {1}, {2}"), GetTypeName(), GetID(), GetPath()); @@ -354,6 +401,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; } @@ -444,6 +492,9 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const } PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); + const StringView path(GetPath()); + ZoneText(*path, path.Length()); Content::WaitForTask(loadingTask, timeoutInMilliseconds); @@ -528,6 +579,7 @@ ContentLoadTask* Asset::createLoadingTask() void Asset::startLoading() { + PROFILE_MEM(ContentAssets); ASSERT(!IsLoaded()); ASSERT(Platform::AtomicRead(&_loadingTask) == 0); auto loadingTask = createLoadingTask(); @@ -627,6 +679,9 @@ void Asset::onLoaded_MainThread() ASSERT(IsInMainThread()); // Send event + ScopeLock lock(_referencesLocker); + for (const auto& e : _references) + e.Item->OnAssetLoaded(this, this); OnLoaded(this); } @@ -640,6 +695,9 @@ 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 c16ea337e..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" @@ -18,6 +19,20 @@ 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: + virtual ~IAssetReference() = default; + + // 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 +63,9 @@ 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; + CriticalSection _referencesLocker; // TODO: convert into a single interlocked exchange for the current thread owning lock + public: /// /// Initializes a new instance of the class. @@ -88,18 +106,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/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..1e36b36ae 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; @@ -410,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[] = @@ -431,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] }); @@ -488,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/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 246bee3b4..df95e58b7 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 e993bbec6..2521966a2 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -5,10 +5,12 @@ #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" #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 @@ -51,6 +53,7 @@ public: AssetReference model = _model.Get(); if (model == nullptr) return true; + PROFILE_MEM(GraphicsMeshes); // Get data BytesContainer data; @@ -334,6 +337,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/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/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 9ca530a53..9d10e0150 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..329696dea 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" @@ -37,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) @@ -1340,6 +1343,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 +1537,7 @@ Asset::LoadResult VisualScript::load() void VisualScript::unload(bool isReloading) { + PROFILE_MEM(ScriptingVisual); #if USE_EDITOR if (isReloading) { @@ -1588,6 +1594,7 @@ AssetChunksFlag VisualScript::getChunksToPreload() const void VisualScript::CacheScriptingType() { + PROFILE_MEM(ScriptingVisual); ScopeLock lock(VisualScriptingBinaryModule::Locker); auto& binaryModule = VisualScriptingModule; @@ -1723,6 +1730,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 +1755,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 +1805,7 @@ void VisualScriptingBinaryModule::OnScriptsReloading() void VisualScriptingBinaryModule::OnEvent(ScriptingObject* object, Span parameters, ScriptingTypeHandle eventType, StringView eventName) { + PROFILE_MEM(ScriptingVisual); if (object) { // Object event @@ -1900,9 +1911,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; } } @@ -1952,6 +1967,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) @@ -2038,6 +2054,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(); @@ -2161,6 +2178,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++) { @@ -2182,6 +2200,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++) { @@ -2379,6 +2398,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); @@ -2419,6 +2439,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/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..2178579cc 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) { @@ -791,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) @@ -1023,6 +1049,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 +1072,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 +1083,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(); @@ -1097,6 +1128,8 @@ void Content::WaitForTask(ContentLoadTask* loadingTask, double timeoutInMillisec localQueue.Clear(); } + PROFILE_CPU_NAMED("Inline"); + ZoneColor(0xffaaaaaa); thread->Run(tmp); } else @@ -1209,6 +1242,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 +1311,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/Content.cs b/Source/Engine/Content/Content.cs index 4fcc0c300..010abbc56 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,33 @@ 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); + } + } + + /// + /// 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/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/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 60f05d38e..1aa434c41 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 @@ -305,6 +310,7 @@ Asset::LoadResult JsonAssetBase::loadAsset() void JsonAssetBase::unload(bool isReloading) { + PROFILE_MEM(ContentAssets); ISerializable::SerializeDocument tmp; Document.Swap(tmp); Data = nullptr; @@ -453,6 +459,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/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/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/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/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..ed8b623ae 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) @@ -1418,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/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/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/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/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 01b5d044c..7cb4a3248 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/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/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index b11d9b845..00614a9ff 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,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/Core/Log.cpp b/Source/Engine/Core/Log.cpp index 215f490be..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" @@ -8,6 +9,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 +44,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 +122,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 +262,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 @@ -306,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..e10fc50a0 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,31 @@ 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 + +#define LOG_FLOOR() Log::Logger::WriteFloor() + extern const Char* ToString(LogType e); namespace Log @@ -186,3 +190,12 @@ 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() {} +#define LOG_FLOOR() {} + +#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/Core/Memory/Allocation.cpp b/Source/Engine/Core/Memory/Allocation.cpp new file mode 100644 index 000000000..c55ab1dab --- /dev/null +++ b/Source/Engine/Core/Memory/Allocation.cpp @@ -0,0 +1,54 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#include "ArenaAllocation.h" +#include "../Math/Math.h" +#include "Engine/Profiler/ProfilerMemory.h" + +void ArenaAllocator::Free() +{ + // Free all pages + Page* page = _first; + while (page) + { +#if COMPILE_WITH_PROFILER + ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1); +#endif + Allocator::Free(page->Memory); + Page* next = page->Next; + Allocator::Free(page); + page = next; + } + + // Unlink + _first = nullptr; +} + +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); +#if COMPILE_WITH_PROFILER + ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1); +#endif + page = (Page*)Allocator::Allocate(sizeof(Page)); + page->Memory = Allocator::Allocate(pageSize); + page->Next = _first; + page->Offset = 0; + 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 += (uint32)size; + + return mem; +} 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 new file mode 100644 index 000000000..af8df2001 --- /dev/null +++ b/Source/Engine/Core/Memory/ArenaAllocation.h @@ -0,0 +1,144 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#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 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 + static void ClearDelete(Dictionary& collection) + { + for (auto it = collection.Begin(); it.IsNotEnd(); ++it) + Memory::DestructItem(it->Value); + 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/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index 4d9159ea9..88f054a30 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -154,9 +154,9 @@ 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)); + //ASSERT(!ObjectsRemovalService::IsInPool(this)); #endif } 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/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/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index a8a3b6b9f..0ce2d8387 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 5d94cf557..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; @@ -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; diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 2bb921f70..752e8bf24 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -525,6 +525,7 @@ DebugDrawService DebugDrawServiceInstance; bool DebugDrawService::Init() { + PROFILE_MEM(Graphics); Context = &GlobalContext; // Init wireframe sphere cache @@ -643,6 +644,7 @@ void DebugDrawService::Update() } PROFILE_CPU(); + PROFILE_MEM(Graphics); // Update lists float deltaTime = Time::Update.DeltaTime.GetTotalSeconds(); 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 3f1a7035e..3dd2897cc 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -76,9 +76,15 @@ 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) { +#if COMPILE_WITH_PROFILER + extern void InitProfilerMemory(const Char* cmdLine, int32 stage); + InitProfilerMemory(cmdLine, 0); +#endif + PROFILE_MEM_BEGIN(Engine); EngineImpl::CommandLine = cmdLine; Globals::MainThreadID = Platform::GetCurrentThreadID(); StartupTime = DateTime::Now(); @@ -106,6 +112,9 @@ int32 Engine::Main(const Char* cmdLine) Platform::Fatal(TEXT("Cannot init platform.")); return -1; } +#if COMPILE_WITH_PROFILER + InitProfilerMemory(cmdLine, 1); +#endif Time::StartupTime = DateTime::Now(); Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory(); @@ -143,7 +152,9 @@ int32 Engine::Main(const Char* cmdLine) { // End LOG(Warning, "Loading project cancelled. Closing..."); +#if LOG_ENABLE Log::Logger::Dispose(); +#endif return 0; } #endif @@ -161,10 +172,11 @@ int32 Engine::Main(const Char* cmdLine) #if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC) EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground; #endif - Log::Logger::WriteFloor(); + LOG_FLOOR(); 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 @@ -180,7 +192,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; } } @@ -205,6 +220,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 @@ -213,6 +232,7 @@ int32 Engine::Main(const Char* cmdLine) OnUpdate(); OnLateUpdate(); Time::OnEndUpdate(); + EngineIdleTime = 0; } // Start physics simulation @@ -541,16 +561,20 @@ void Engine::OnExit() #if COMPILE_WITH_PROFILER ProfilerCPU::Dispose(); ProfilerGPU::Dispose(); + ProfilerMemory::Enabled = false; #endif +#if LOG_ENABLE // Close logging service Log::Logger::Dispose(); +#endif Platform::Exit(); } void EngineImpl::InitLog() { +#if LOG_ENABLE // Initialize logger Log::Logger::Init(); @@ -604,6 +628,7 @@ void EngineImpl::InitLog() Platform::LogInfo(); LOG_FLUSH(); +#endif } void EngineImpl::InitPaths() 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/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 368c67132..7d16b4752 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -195,14 +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++) - managedArray[i] = span[i] != IntPtr.Zero ? (T)ManagedHandle.FromIntPtr(span[i]).Target : default; - return managedArray; + if (buffer == null || buffer.Length < ptrArray.Length) + buffer = new T[ptrArray.Length]; + for (int i = 0; i < ptrArray.Length; i++) + { + IntPtr ptr = span[i]; + buffer[i] = ptr != IntPtr.Zero ? (T)ManagedHandle.FromIntPtr(ptr).Target : default; + } + return buffer; } /// diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index c0e0b0b0e..4fc576ff8 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; @@ -1334,6 +1339,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/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index 0e71ea8b3..f8372572e 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() @@ -242,6 +245,15 @@ bool GPUBuffer::Init(const GPUBufferDescription& desc) return true; } +#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; } @@ -476,6 +488,15 @@ GPUResourceType GPUBuffer::GetResourceType() const void GPUBuffer::OnReleaseGPU() { +#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/GPUBufferDescription.cs b/Source/Engine/Graphics/GPUBufferDescription.cs index 290e43a7a..107d17b3e 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.cs +++ b/Source/Engine/Graphics/GPUBufferDescription.cs @@ -1,9 +1,41 @@ // 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); + } + } + + /// + /// 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 { /// diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index e4bbc170a..18b9cdffc 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -648,8 +648,26 @@ 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); DrawBegin(); auto context = GetMainContext(); 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 }; /// diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 7ffba3957..bf17970ea 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,9 +98,10 @@ void Graphics::DisposeDevice() bool GraphicsService::Init() { ASSERT(GPUDevice::Instance == nullptr); + PROFILE_MEM(Graphics); // Create and initialize graphics device - Log::Logger::WriteFloor(); + LOG_FLOOR(); LOG(Info, "Creating Graphics Device..."); PixelFormatExtensions::Init(); GPUDevice* device = nullptr; @@ -214,7 +216,7 @@ bool GraphicsService::Init() { return true; } - Log::Logger::WriteFloor(); + LOG_FLOOR(); return false; } diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index c15ff5ef2..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) @@ -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; @@ -112,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) 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/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index e31697f77..2fa5a8e5f 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 { @@ -604,10 +605,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; } @@ -638,6 +640,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/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/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/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) { 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/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/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index df45d981c..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)) { @@ -417,6 +424,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 59b025b70..3b6fca968 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) @@ -501,6 +504,17 @@ bool GPUTexture::Init(const GPUTextureDescription& desc) return true; } +#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) { @@ -589,6 +603,17 @@ GPUResourceType GPUTexture::GetResourceType() const void GPUTexture::OnReleaseGPU() { +#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; } @@ -603,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); @@ -712,6 +738,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) @@ -798,6 +825,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/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/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index ad39b777d..411d9dd92 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" @@ -764,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))); @@ -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..3856a7cd0 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp @@ -3,10 +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 "GPUContextDX11.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Profiler/ProfilerMemory.h" GPUSwapChainDX11::GPUSwapChainDX11(GPUDeviceDX11* device, Window* window) : GPUResourceDX11(device, StringView::Empty) @@ -60,9 +62,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() @@ -137,6 +141,9 @@ GPUTextureView* GPUSwapChainDX11::GetBackBufferView() void GPUSwapChainDX11::Present(bool vsync) { + PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); + // Present frame ASSERT(_swapChain); UINT presentFlags = 0; @@ -262,6 +269,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/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/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index dd2bc3da4..9cb285ac1 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) { @@ -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]); 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..83ecf020d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp @@ -6,6 +6,8 @@ #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) { @@ -71,6 +73,7 @@ void GPUSwapChainDX12::OnReleaseGPU() #endif // Release data + PROFILE_MEM_DEC(Graphics, _memoryUsage); releaseBackBuffer(); _backBuffers.Resize(0); if (_swapChain) @@ -79,6 +82,7 @@ void GPUSwapChainDX12::OnReleaseGPU() _swapChain = nullptr; } _width = _height = 0; + _memoryUsage = 0; } void GPUSwapChainDX12::releaseBackBuffer() @@ -244,6 +248,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 @@ -360,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/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/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/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/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(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 359ac0993..f2d0aad7d 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()) @@ -2089,22 +2094,26 @@ void GPUDeviceVulkan::WaitForGPU() if (Device != VK_NULL_HANDLE) { PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); VALIDATE_VULKAN_RESULT(vkDeviceWaitIdle(Device)); } } 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 +2124,7 @@ GPUTimerQuery* GPUDeviceVulkan::CreateTimerQuery() GPUBuffer* GPUDeviceVulkan::CreateBuffer(const StringView& name) { + PROFILE_MEM(GraphicsBuffers); return New(this, name); } @@ -2135,6 +2145,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 5aa79ed75..c8a0ba173 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() @@ -423,6 +426,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; } @@ -560,6 +564,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/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp index 961799a42..604b8a612 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp @@ -248,10 +248,14 @@ 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 } bool RenderToolsVulkan::HasExtension(const Array& extensions, const char* name) diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index eaff89ad5..1f33f9fef 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 @@ -90,12 +91,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()) { @@ -167,6 +170,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; @@ -185,6 +189,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; @@ -194,6 +199,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; @@ -203,6 +209,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; @@ -219,6 +226,7 @@ void Mouse::OnMouseMoveRelative(const Float2& positionRelative, Window* target) void Mouse::OnMouseLeave(Window* target) { + PROFILE_MEM(Input); Event& e = _queue.AddOne(); e.Type = EventType::MouseLeave; e.Target = target; @@ -226,6 +234,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; @@ -327,6 +336,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; @@ -337,6 +347,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; @@ -347,6 +358,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; @@ -629,6 +641,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++) @@ -648,6 +661,7 @@ void Input::SetInputMappingFromSettings(const JsonAssetReference& void Input::SetInputMappingToDefaultSettings() { + PROFILE_MEM(Input); InputSettings* settings = InputSettings::Get(); if (settings) { @@ -710,6 +724,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); @@ -726,6 +741,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); @@ -741,6 +757,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); @@ -756,6 +773,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); @@ -774,6 +792,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); @@ -788,6 +807,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); @@ -802,6 +822,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); @@ -816,6 +837,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); @@ -833,6 +855,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 0c0a98e14..551f6fba9 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -466,12 +466,73 @@ 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; + +#if USE_EDITOR + // Inform Editor beforehand + Level::callActorEvent(Level::ActorEventType::OnActorDestroyChildren, this, nullptr); +#endif + + 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); } } @@ -1125,9 +1186,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()); + } } } } @@ -1276,7 +1341,6 @@ void Actor::OnActiveChanged() if (wasActiveInTree != IsActiveInHierarchy()) OnActiveInTreeChanged(); - //if (GetScene()) Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, this, nullptr); } @@ -1307,7 +1371,6 @@ void Actor::OnActiveInTreeChanged() void Actor::OnOrderInParentChanged() { - //if (GetScene()) Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, this, nullptr); } diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 7863578c6..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) @@ -27,16 +28,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() @@ -86,7 +84,8 @@ void AnimatedModel::PreInitSkinningData() { if (!SkinnedModel || !SkinnedModel->IsLoaded()) return; - + PROFILE_CPU(); + PROFILE_MEM(Animations); ScopeLock lock(SkinnedModel->Locker); SetupSkinningData(); @@ -96,28 +95,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(); @@ -137,6 +138,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()) @@ -586,6 +594,7 @@ void AnimatedModel::SyncParameters() } else { + PROFILE_MEM(Animations); ScopeLock lock(AnimationGraph->Locker); // Clone the parameters @@ -878,6 +887,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 859d89212..deadf8c7c 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; @@ -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). /// @@ -416,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/Level/Level.cpp b/Source/Engine/Level/Level.cpp index a96ba936a..49bef81c3 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -18,15 +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" @@ -77,6 +77,13 @@ enum class SceneEventType OnSceneUnloaded = 7, }; +enum class SceneResult +{ + Success, + Failed, + Wait, +}; + class SceneAction { public: @@ -84,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; } }; @@ -106,6 +114,99 @@ 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 +{ +public: + struct Args + { + 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 = 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; + TimeSlicer StageSlicer; + + SceneLoader(bool asyncLoad = false) + : AsyncLoad(asyncLoad) + , AsyncJobs(JobSystem::GetThreadsCount() > 1) + , Modifier(Cache::ISerializeModifier.GetUnscoped()) + , Context(Modifier) + { + } + + ~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 { Array _sceneActions; @@ -118,6 +219,10 @@ namespace LevelImpl void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId); void flushActions(); + 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(); bool saveScene(Scene* scene); @@ -146,16 +251,21 @@ public: }; LevelService LevelServiceInstanceService; +extern double EngineIdleTime; CriticalSection Level::ScenesLock; Array Level::Scenes; bool Level::TickEnabled = true; +float Level::StreamingFrameBudget = 0.3f; Delegate Level::ActorSpawned; Delegate Level::ActorDeleted; 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; @@ -248,6 +358,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) \ @@ -392,40 +503,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, &context.TimeBudget); } }; @@ -439,12 +532,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; } }; @@ -455,9 +548,9 @@ public: { } - bool Do() const override + SceneResult Do(Context& context) override { - return unloadScenes(); + return unloadScenes() ? SceneResult::Failed : SceneResult::Success; } }; @@ -473,14 +566,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; } }; @@ -493,7 +586,7 @@ public: { } - bool Do() const override + SceneResult Do(Context& context) override { // Reloading scripts workflow: // - save scenes (to temporary files) @@ -504,6 +597,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(); @@ -553,7 +647,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()); } @@ -598,16 +692,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); @@ -616,7 +711,7 @@ public: LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast((DateTime::NowUTC() - startTime).GetTotalMilliseconds())); Level::ScriptsReloadEnd(); - return false; + return SceneResult::Success; } }; @@ -648,9 +743,9 @@ public: { } - bool Do() const override + SceneResult Do(Context& context) override { - return spawnActor(TargetActor, ParentActor); + return spawnActor(TargetActor, ParentActor) ? SceneResult::Failed : SceneResult::Success; } }; @@ -664,9 +759,9 @@ public: { } - bool Do() const override + SceneResult Do(Context& context) override { - return deleteActor(TargetActor); + return deleteActor(TargetActor) ? SceneResult::Failed : SceneResult::Success; } }; @@ -759,18 +854,52 @@ 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 } } 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; + 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; +#endif +#if BUILD_DEBUG + // Throttle up in Debug + context.TimeBudget *= 1.2f; +#endif + if (context.TimeBudget <= 0.0f) + context.TimeBudget = MAX_float; // Unlimited if 0 + context.TimeBudget = Math::Max(context.TimeBudget, 0.001f); // Minimum 1ms - 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); + } } } @@ -784,6 +913,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); @@ -809,6 +939,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--) { @@ -818,26 +949,27 @@ bool LevelImpl::unloadScenes() return false; } -bool Level::loadScene(JsonAsset* sceneAsset) +SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset, float* timeBudget) { // 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(), timeBudget); } -bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene) +SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene, float* timeBudget) { if (sceneData.IsInvalid()) { LOG(Error, "Missing scene data."); - return true; + return SceneResult::Failed; } + PROFILE_MEM(Level); // Parse scene JSON file rapidjson_flax::Document document; @@ -848,39 +980,123 @@ 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, timeBudget); } -bool Level::loadScene(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()) { 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, nullptr, timeBudget); } -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"); - if (outScene) - *outScene = nullptr; + PROFILE_MEM(Level); #if USE_EDITOR ContentDeprecated::Clear(); #endif - LOG(Info, "Loading scene..."); - Stopwatch stopwatch; - _lastSceneLoadTime = DateTime::Now(); + SceneResult 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(args); + time.Stop(); + const float delta = time.GetTotalSeconds(); + loader.TotalTime += delta; + timeLeft -= delta; + if (timeLeft < 0.0f && result == SceneResult::Success) + { + result = SceneResult::Wait; + break; + } + } + if (outScene) + *outScene = loader.Scene; + return result; +} - // Here whole scripting backend should be loaded for current project - // Later scripts will setup attached scripts and restore initial vars +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) + { + 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; + } +} + +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."); @@ -893,166 +1109,195 @@ 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) + if (args.EngineBuild < 6000) { LOG(Error, "Invalid serialized engine build."); - return true; + return SceneResult::Failed; } - if (!data.IsArray()) + if (!args.Data.IsArray()) { LOG(Error, "Invalid Data member."); - return true; + 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 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; + 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) { - 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) { - 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); - 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) { - 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 - 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); + StageSlicer.BeginSync(args.TimeBudget, dataCount, 1); // start from 1. at index [0] was scene + while (StageSlicer.Index < StageSlicer.Count) + { + 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; - // /\ all above this has to be done on multiple threads at once - // \/ all below this has to be done on an any thread + auto result = StageSlicer.End(); + if (result != SceneResult::Wait) + NextStage(); + return result; +} +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); @@ -1061,69 +1306,93 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou { 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(ScenesLock); - Scenes.Add(scene); - SceneBeginData beginData; - scene->BeginPlay(&beginData); - beginData.OnDone(); - } + // Link scene + ScopeLock lock(Level::ScenesLock); + Level::Scenes.Add(Scene); + + // 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()); @@ -1132,17 +1401,18 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou 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 - return false; + NextStage(); + return SceneResult::Success; } bool LevelImpl::saveScene(Scene* scene) @@ -1165,6 +1435,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 +1479,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); @@ -1261,7 +1533,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) @@ -1307,9 +1580,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; @@ -1359,7 +1633,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); @@ -1371,7 +1646,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); @@ -1381,7 +1657,6 @@ Scene* Level::LoadSceneFromBytes(const BytesContainer& data) bool Level::LoadSceneAsync(const Guid& id) { - // Check ID if (!id.IsValid()) { Log::ArgumentException(); @@ -1397,7 +1672,7 @@ bool Level::LoadSceneAsync(const Guid& id) } ScopeLock lock(_sceneActionsLocker); - _sceneActions.Enqueue(New(id, sceneAsset)); + _sceneActions.Enqueue(New(id, sceneAsset, true)); return false; } @@ -1665,8 +1940,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; } diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index f07a20c51..484ba35b8 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: /// @@ -544,13 +549,13 @@ private: OnActorOrderInParentChanged = 3, OnActorNameChanged = 4, OnActorActiveChanged = 5, +#if USE_EDITOR + OnActorDestroyChildren = 6, +#endif }; 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); +#if USE_EDITOR + API_EVENT(Internal) static Delegate ActorDestroyChildren; +#endif }; 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); /// diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 445447cd1..e55dbd43f 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,7 +42,8 @@ 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]; @@ -125,7 +127,7 @@ void SceneRendering::CollectPostFxVolumes(RenderContext& renderContext) void SceneRendering::Clear() { - ScopeLock lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker, true); for (auto* listener : _listeners) { listener->OnSceneRenderingClear(this); @@ -134,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 @@ -143,18 +147,21 @@ void SceneRendering::AddActor(Actor* a, int32& key) { if (key != -1) return; + PROFILE_MEM(Graphics); const int32 category = a->_drawCategory; - ScopeLock lock(Locker); + ConcurrentSystemLocker::WriteScope lock(Locker, true); 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(); @@ -167,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; @@ -186,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, true); auto& list = Actors[category]; if (list.Count() > key) // Ignore invalid key softly (eg. list after batch clear during scene unload) { @@ -197,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; @@ -214,6 +222,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/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index 043f5079e..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; @@ -100,8 +100,9 @@ public: }; Array Actors[MAX]; + Array FreeActors[MAX]; Array PostFxProviders; - CriticalSection Locker; + ConcurrentSystemLocker Locker; private: #if USE_EDITOR 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/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..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(); } @@ -34,9 +36,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); @@ -47,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) @@ -171,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/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index 62017ad9e..5593d732a 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -4,6 +4,8 @@ #include "NavMeshRuntime.h" #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" @@ -16,8 +18,8 @@ NavMesh::NavMesh(const SpawnParams& params) : Actor(params) , IsDataDirty(false) + , DataAsset(this) { - DataAsset.Loaded.Bind(this); } void NavMesh::SaveNavMesh() @@ -99,12 +101,17 @@ 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()) return; ScopeLock lock(DataAsset->Locker); + PROFILE_MEM(Navigation); // Remove added tiles if (_navMeshActive) @@ -125,6 +132,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/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 7bfa40da3..e92173846 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -19,9 +19,11 @@ #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" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Level.h" #include @@ -731,6 +733,7 @@ public: bool Run() override { PROFILE_CPU_NAMED("BuildNavMeshTile"); + PROFILE_MEM(Navigation); const auto navMesh = NavMesh.Get(); if (!navMesh) return false; @@ -1153,6 +1156,7 @@ void ClearNavigation(Scene* scene) void NavMeshBuilder::Update() { + PROFILE_MEM(Navigation); ScopeLock lock(NavBuildQueueLocker); // Process nav mesh building requests and kick the tasks @@ -1203,7 +1207,7 @@ void NavMeshBuilder::Build(Scene* scene, float timeoutMs) } PROFILE_CPU_NAMED("NavMeshBuilder"); - + PROFILE_MEM(Navigation); ScopeLock lock(NavBuildQueueLocker); BuildRequest req; @@ -1240,7 +1244,7 @@ void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float t } PROFILE_CPU_NAMED("NavMeshBuilder"); - + PROFILE_MEM(Navigation); ScopeLock lock(NavBuildQueueLocker); BuildRequest req; 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 640b5dd9b..2758077c6 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 @@ -325,6 +326,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; @@ -385,6 +387,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 @@ -416,6 +419,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 570db86c7..34983652f 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..784bbf51e 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -14,7 +14,9 @@ #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" +#include "Engine/Threading/Threading.h" float NetworkManager::NetworkFPS = 60.0f; NetworkPeer* NetworkManager::Peer = nullptr; @@ -414,6 +416,7 @@ NetworkManagerService NetworkManagerServiceInstance; bool StartPeer() { PROFILE_CPU(); + PROFILE_MEM(Networking); ASSERT_LOW_LAYER(!NetworkManager::Peer); NetworkManager::State = NetworkConnectionState::Connecting; NetworkManager::StateChanged(); @@ -504,6 +507,7 @@ NetworkClient* NetworkManager::GetClient(uint32 clientId) bool NetworkManager::StartServer() { PROFILE_CPU(); + PROFILE_MEM(Networking); Stop(); LOG(Info, "Starting network manager as server"); @@ -529,6 +533,7 @@ bool NetworkManager::StartServer() bool NetworkManager::StartClient() { PROFILE_CPU(); + PROFILE_MEM(Networking); Stop(); LOG(Info, "Starting network manager as client"); @@ -553,6 +558,7 @@ bool NetworkManager::StartClient() bool NetworkManager::StartHost() { PROFILE_CPU(); + PROFILE_MEM(Networking); Stop(); LOG(Info, "Starting network manager as host"); @@ -586,6 +592,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 +639,7 @@ void NetworkManager::Stop() void NetworkKeys::SendPending() { PROFILE_CPU(); + PROFILE_MEM(Networking); ScopeLock lock(Lock); // Add new keys @@ -718,6 +726,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..93ccef55c 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" @@ -15,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) @@ -380,6 +379,7 @@ void ParticleEffect::Sync() Instance.ClearState(); return; } + PROFILE_MEM(Particles); Instance.Sync(system); @@ -498,6 +498,7 @@ void ParticleEffect::CacheModifiedParameters() { if (_parameters.IsEmpty()) return; + PROFILE_MEM(Particles); _parametersOverrides.Clear(); auto& parameters = GetParameters(); for (auto& param : parameters) @@ -516,6 +517,7 @@ void ParticleEffect::ApplyModifiedParameters() { if (_parametersOverrides.IsEmpty()) return; + PROFILE_MEM(Particles); // Parameters getter applies the parameters overrides if (_parameters.IsEmpty()) @@ -538,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) @@ -658,6 +664,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/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/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index 558c0172f..3e1847b67 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -15,6 +15,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" @@ -43,6 +44,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); @@ -103,6 +105,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 b184bfbda..a55cddd2a 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); @@ -537,6 +542,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()) @@ -632,6 +638,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) if (_paint.IsEmpty()) return; PROFILE_CPU(); + PROFILE_MEM(Physics); // Get mesh data const ModelInstanceActor::MeshReference mesh = GetMesh(); @@ -808,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(); @@ -919,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(); @@ -991,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; 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/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/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/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 b023b49f7..99b54e8fc 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 @@ -81,7 +82,6 @@ struct ActionDataPhysX struct ScenePhysX { PxScene* Scene = nullptr; - PxCpuDispatcher* CpuDispatcher = nullptr; PxControllerManager* ControllerManager = nullptr; void* ScratchMemory = nullptr; Vector3 Origin = Vector3::Zero; @@ -117,6 +117,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); } @@ -540,6 +541,7 @@ namespace { PxFoundation* Foundation = nullptr; PxPhysics* PhysX = nullptr; + PxDefaultCpuDispatcher* CpuDispatcher = nullptr; #if WITH_PVD PxPvd* PVD = nullptr; #endif @@ -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(); @@ -1731,6 +1734,7 @@ void PhysicsBackend::Shutdown() #if WITH_PVD RELEASE_PHYSX(PVD); #endif + RELEASE_PHYSX(CpuDispatcher); RELEASE_PHYSX(Foundation); SceneOrigins.Clear(); } @@ -1788,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, 8); + CpuDispatcher = PxDefaultCpuDispatcherCreate(threads); + CHECK_INIT(CpuDispatcher, "PxDefaultCpuDispatcherCreate failed!"); + } + sceneDesc.cpuDispatcher = CpuDispatcher; } switch (settings.BroadPhaseType) { @@ -1852,7 +1860,6 @@ void PhysicsBackend::DestroyScene(void* scene) } #endif RELEASE_PHYSX(scenePhysX->ControllerManager); - SAFE_DELETE(scenePhysX->CpuDispatcher); Allocator::Free(scenePhysX->ScratchMemory); scenePhysX->Scene->release(); @@ -1861,6 +1868,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(); @@ -1893,8 +1901,26 @@ 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); auto scenePhysX = (ScenePhysX*)scene; { @@ -1910,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++; @@ -3893,6 +3927,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/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.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 5104dd5f8..c6c689195 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -2,6 +2,13 @@ #if PLATFORM_MAC || 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" #include "Engine/Core/Log.h" @@ -50,6 +57,25 @@ #include #endif +// System includes break those defines +#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; double SecondsPerCycle; @@ -224,8 +250,17 @@ MemoryStats ApplePlatform::GetMemoryStats() ProcessMemoryStats ApplePlatform::GetProcessMemoryStats() { ProcessMemoryStats result; - result.UsedPhysicalMemory = 1024; - result.UsedVirtualMemory = 1024; +#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/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h index cf91b8e22..003cec91a 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); @@ -61,10 +65,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.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 7a93c24ed..2f6309935 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" @@ -219,6 +220,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) @@ -236,6 +241,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); @@ -316,10 +325,11 @@ 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(); - Log::Logger::WriteFloor(); + LOG_FLOOR(); LOG(Error, ""); LOG(Error, "Critical error! Reason: {0}", msg); LOG(Error, ""); @@ -387,6 +397,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()) { @@ -399,13 +415,14 @@ 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 // Show error message if (Engine::ReportCrash.IsBinded()) diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index 97928c376..286247cbc 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(); @@ -246,6 +246,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 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. /// @@ -257,7 +263,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. @@ -267,7 +273,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. @@ -293,7 +299,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; /// @@ -324,12 +330,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/Base/WindowBase.cpp b/Source/Engine/Platform/Base/WindowBase.cpp index 4b91bdac0..cd96dca41 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" @@ -213,6 +214,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); } @@ -220,6 +222,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); } @@ -227,6 +230,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); } @@ -234,6 +238,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); } @@ -241,6 +246,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); } @@ -248,6 +254,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); } @@ -255,6 +262,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); } @@ -262,6 +270,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); } @@ -276,6 +285,7 @@ void WindowBase::OnMouseMoveRelative(const Float2& mousePositionRelative) void WindowBase::OnMouseLeave() { PROFILE_CPU_NAMED("GUI.OnMouseLeave"); + PROFILE_MEM(UI); MouseLeave(); INVOKE_EVENT_PARAMS_0(OnMouseLeave); } @@ -283,6 +293,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); } @@ -290,6 +301,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); } @@ -297,6 +309,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); } @@ -407,6 +420,7 @@ bool WindowBase::GetMouseButtonUp(MouseButton button) const void WindowBase::OnShow() { PROFILE_CPU_NAMED("GUI.OnShow"); + PROFILE_MEM(UI); INVOKE_EVENT_PARAMS_0(OnShow); Shown(); } @@ -414,10 +428,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); } @@ -469,6 +486,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); } @@ -476,6 +494,7 @@ void WindowBase::OnUpdate(float dt) void WindowBase::OnDraw() { PROFILE_CPU_NAMED("GUI.OnDraw"); + PROFILE_MEM(UI); INVOKE_EVENT_PARAMS_0(OnDraw); Draw(); } @@ -483,6 +502,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/Defines.h b/Source/Engine/Platform/Defines.h index f0802f930..6105cf8b3 100644 --- a/Source/Engine/Platform/Defines.h +++ b/Source/Engine/Platform/Defines.h @@ -210,28 +210,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/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index f5fced5d3..d06cfd369 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -3061,8 +3061,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/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index b8fdc6268..f101fffed 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/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 81cb015eb..10ae71d7a 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -491,6 +491,7 @@ 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); @@ -498,6 +499,7 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) lineView = StringView(line.Get(), line.Length() - 1); Log::Logger::Write(LogType::Info, lineView); } +#endif [[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } } @@ -518,6 +520,7 @@ 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); @@ -525,6 +528,7 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) lineView = StringView(line.Get(), line.Length() - 1); Log::Logger::Write(LogType::Error, lineView); } +#endif [[stderrPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } } 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/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index a61d7c36d..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); @@ -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, numBytes); +#endif + return ptr; } void Win32Platform::FreePages(void* ptr) { +#if COMPILE_WITH_PROFILER + OnMemoryFree(ptr); +#endif VirtualFree(ptr, 0, MEM_RELEASE); } 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); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index c96304777..85c282a38 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -258,6 +258,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 + #if !PLATFORM_SDL LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) @@ -523,6 +554,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(); @@ -564,14 +649,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() @@ -648,25 +727,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; @@ -725,20 +804,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); @@ -1072,8 +1139,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/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; } 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/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/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/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 new file mode 100644 index 000000000..c936ff5b2 --- /dev/null +++ b/Source/Engine/Profiler/ProfilerMemory.cpp @@ -0,0 +1,489 @@ +// 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/Scripting/ManagedCLR/MCore.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."); +static_assert(sizeof(ProfilerMemory::Groups) == sizeof(uint8), "Fix memory profiler groups to fit a single byte."); + +// Compact name storage. +struct GroupNameBuffer +{ + Char Buffer[40]; + char Ansi[40]; + + template + void Set(const T* str, bool autoFormat = false) + { + int32 max = StringUtils::Length(str), dst = 0; + T prev = 0; + for (int32 i = 0; i < max && dst < ARRAY_COUNT(Buffer) - 2; i++) + { + T cur = (T)str[i]; + if (autoFormat && StringUtils::IsUpper(cur) && StringUtils::IsLower(prev)) + { + Ansi[dst] = '/'; + Buffer[dst++] = '/'; + } + Ansi[dst] = (char)cur; + Buffer[dst++] = (Char)cur; + prev = cur; + } + Buffer[dst] = 0; + Ansi[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++; + 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] = {}; +#ifdef USE_TRACY_MEMORY_PLOTS + 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; + + void Dump(StringBuilder& output, const int32 maxCount) + { + // 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(); + } + + // 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'")); + } + +#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, (int64_t)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 + 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]); + UpdateGroupTracyPlot((ProfilerMemory::Groups)parent); + UPDATE_PEEK(parent); + parent = GroupParents[parent]; + } + } + + FORCE_INLINE void SubGroupMemory(ProfilerMemory::Groups group, int64 add) + { + // Group itself + Platform::InterlockedAdd(&GroupMemory[(int32)group], add); + Platform::InterlockedDecrement(&GroupMemoryCount[(int32)group]); + UpdateGroupTracyPlot(group); + + // Total memory + 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) + { + Platform::InterlockedAdd(&GroupMemory[parent], add); + Platform::InterlockedDecrement(&GroupMemoryCount[parent]); + UpdateGroupTracyPlot((ProfilerMemory::Groups)parent); + parent = GroupParents[parent]; + } + } +} + +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")); + + // 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); + 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); + INIT_PARENT(Graphics, GraphicsCommands); + 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); + INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCHeap); +#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"); + RENAME_GROUP(ScriptingCSharpGCCommitted, "Scripting/CSharp/GC/Committed"); + RENAME_GROUP(ScriptingCSharpGCHeap, "Scripting/CSharp/GC/Heap"); +#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() +{ + // 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(); + + // 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 = GetGroupStack(); + stack.Push(group); +} + +void ProfilerMemory::EndGroup() +{ + auto& stack = GetGroupStack(); + 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); + 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."); + 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) +{ +#if LOG_ENABLE + bool file = options.Contains(TEXT("file"), StringSearchCase::IgnoreCase); + StringBuilder output; + int32 maxCount = 20; + if (file || options.Contains(TEXT("all"), StringSearchCase::IgnoreCase)) + 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()); +#endif +} + +void ProfilerMemory::OnMemoryAlloc(void* ptr, uint64 size) +{ + ASSERT_LOW_LAYER(Enabled && ptr); + auto& stack = GetGroupStack(); + if (stack.SkipRecursion) + return; + stack.SkipRecursion = true; + + // Register pointer + PointerData ptrData; + ptrData.Size = (uint32)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)Groups::Malloc], add); + Platform::InterlockedIncrement(&GroupMemoryCount[(int32)Groups::Malloc]); + UPDATE_PEEK(ProfilerMemory::Groups::Malloc); + + stack.SkipRecursion = false; +} + +void ProfilerMemory::OnMemoryFree(void* ptr) +{ + ASSERT_LOW_LAYER(Enabled && ptr); + auto& stack = GetGroupStack(); + 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)Groups::Malloc], add); + Platform::InterlockedDecrement(&GroupMemoryCount[(int32)Groups::Malloc]); + } + + stack.SkipRecursion = false; +} + +void ProfilerMemory::OnGroupUpdate(Groups group, int64 sizeDelta, int64 countDelta) +{ + Platform::InterlockedAdd(&GroupMemory[(int32)group], sizeDelta); + Platform::InterlockedAdd(&GroupMemoryCount[(int32)group], countDelta); + UPDATE_PEEK(group); +} + +#endif diff --git a/Source/Engine/Profiler/ProfilerMemory.h b/Source/Engine/Profiler/ProfilerMemory.h new file mode 100644 index 000000000..5dddb912b --- /dev/null +++ b/Source/Engine/Profiler/ProfilerMemory.h @@ -0,0 +1,298 @@ +// 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, + // Profiling tool memory overhead. + Profiler, + + // Total memory allocated via dynamic memory allocations. + Malloc, + // 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. + 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 index buffers allocated by models). + GraphicsMeshes, + // Total shaders memory usage (shaders bytecode, PSOs data). + GraphicsShaders, + // Total materials memory usage (constant buffers, parameters data). + GraphicsMaterials, + // Total 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 audio 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 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, + + // 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 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, + + // 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, + + // 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, + + MAX + }; + + /// + /// The memory groups array wrapper 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 (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); + +public: + /// + /// The profiling tools usage flag. Can be used to disable profiler. Run engine with '-mem' command line to activate it from start. + /// + API_FIELD(ReadOnly) static bool Enabled; + + static void OnMemoryAlloc(void* ptr, uint64 size); + static void OnMemoryFree(void* ptr); + static void OnGroupUpdate(Groups group, int64 sizeDelta, int64 countDelta); + +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 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/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 81cee0cb6..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(); @@ -342,6 +338,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/Renderer/ColorGradingPass.cpp b/Source/Engine/Renderer/ColorGradingPass.cpp index 3b531b30f..322e7d591 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) { } @@ -90,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/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/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index a46568f97..e0d227b6b 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, true); OnSceneRenderingDirty(a->GetBox()); } } @@ -403,6 +405,7 @@ public: { if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { + ConcurrentSystemLocker::WriteScope lock(Locker, true); OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); OnSceneRenderingDirty(a->GetBox()); } @@ -412,12 +415,14 @@ public: { if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { + 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(); } @@ -715,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/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..eaf7a53ca 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -2,10 +2,11 @@ #include "ProbesRenderer.h" #include "Renderer.h" +#include "RenderList.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 +15,48 @@ #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,108 +96,85 @@ 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; + int32 _workStep; + float _customCullingNear; - bool _isReady = false; AssetReference _shader; GPUPipelineState* _psFilterFace = nullptr; SceneRenderTask* _task = nullptr; GPUTexture* _output = nullptr; GPUTexture* _probe = nullptr; GPUTexture* _tmpFace = nullptr; - 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(); + bool InitShader(); void Update() override; void Dispose() override; + void Bake(const ProbeEntry& e); + +private: + void OnRender(RenderTask* task, GPUContext* context); + void OnSetupRender(RenderContext& renderContext); +#if COMPILE_WITH_DEV_ENV + bool _initShader = false; + void OnShaderReloading(Asset* obj) + { + _initShader = true; + SAFE_DELETE_GPU_RESOURCE(_psFilterFace); + } +#endif }; 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); +int32 ProbesRenderer::MaxWorkPerFrame = 1; +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 +185,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,120 +199,89 @@ 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 COMPILE_WITH_DEV_ENV + _shader->OnReloading.Bind(this); +#endif } 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; - } - - // Create pipeline stages - _psFilterFace = GPUDevice::Instance->CreatePipelineState(); - GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; - { - psDesc.PS = shader->GetPS("PS_FilterFace"); - if (_psFilterFace->Init(psDesc)) - return true; - } + _initFailed |= InitShader(); // 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->ActorsSource = ActorsSources::ScenesAndCustomActors; 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); + task->SetupRender.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() +bool ProbesRendererService::InitShader() { - 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; + 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); + // Calculate time delta since last update auto timeNow = Time::Update.UnscaledTime; auto timeSinceUpdate = timeNow - _lastProbeUpdate; @@ -325,35 +292,33 @@ 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; + _workStep = 0; + _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]; @@ -366,40 +331,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 + _workStep = 0; _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(_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)) { @@ -412,20 +402,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; @@ -434,74 +423,93 @@ void ProbesRenderer::OnRender(RenderTask* task, GPUContext* context) // Canceled return; } - + ASSERT(_updateFrameNumber == 0); 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 - float customCullingNear = -1; const int32 probeResolution = _current.GetResolution(); const PixelFormat probeFormat = _current.GetFormat(); - if (_current.Type == EntryType::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(&fixFarPlaneTreeExecute); - 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 == EntryType::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() || 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 { @@ -511,12 +519,17 @@ void ProbesRenderer::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; @@ -548,11 +561,18 @@ void ProbesRenderer::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; @@ -560,13 +580,19 @@ 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; } } + +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/ProbesRenderer.h b/Source/Engine/Renderer/ProbesRenderer.h index 5c4e011e4..73d8b4132 100644 --- a/Source/Engine/Renderer/ProbesRenderer.h +++ b/Source/Engine/Renderer/ProbesRenderer.h @@ -2,75 +2,35 @@ #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; - - int32 GetBakeQueueSize(); - - 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(); + static TimeSpan ReleaseTimeout; /// - /// Init probes content + /// 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. /// - /// True if cannot init service - static bool Init(); + static int32 MaxWorkPerFrame; - /// - /// Release probes content - /// - static void Release(); + static Delegate OnRegisterBake; + + static Delegate OnFinishBake; public: /// @@ -78,15 +38,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); }; 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/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/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 7fd12ba73..fd02b133f 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()); @@ -376,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/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 817f25eef..6029b399d 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) { } @@ -54,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"); @@ -110,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 @@ -122,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; @@ -195,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 diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 8d61e2bb5..07feedf10 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(ScriptingCSharp); + // Create native object ScriptingTypeHandle managedTypeHandle = params.Type; const ScriptingType* managedTypePtr = &managedTypeHandle.GetType(); @@ -812,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]; @@ -932,6 +935,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) { #if !COMPILE_WITHOUT_CSHARP PROFILE_CPU(); + PROFILE_MEM(ScriptingCSharp); ASSERT(ClassToTypeIndex.IsEmpty()); ScopeLock lock(Locker); @@ -1025,9 +1029,10 @@ 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(ScriptingCSharp); // 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; } @@ -1086,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); @@ -1175,14 +1184,14 @@ 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); } } void ManagedBinaryModule::OnUnloaded(MAssembly* assembly) { PROFILE_CPU(); + PROFILE_MEM(ScriptingCSharp); // Clear managed-only types Types.Resize(_firstManagedTypeIndex); @@ -1495,9 +1504,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 +1566,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/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/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/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..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" /// @@ -17,21 +18,20 @@ 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; + MAssembly* _assembly; - StringAnsi _fullname; - - 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; @@ -47,12 +47,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 +65,7 @@ public: /// /// Gets the parent assembly. /// - const MAssembly* GetAssembly() const + FORCE_INLINE MAssembly* GetAssembly() const { return _assembly; } @@ -72,7 +73,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 +81,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 +168,9 @@ public: /// /// Gets if class is generic /// - bool IsGeneric() const + FORCE_INLINE bool IsGeneric() const { - return _fullname.FindLast('`') != -1; + return _isGeneric != 0; } /// @@ -242,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. @@ -257,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. @@ -270,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. @@ -285,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: /// @@ -326,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/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index db184afd9..4300434e3 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" @@ -70,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(); @@ -80,6 +92,7 @@ bool MAssembly::Load(const String& assemblyPath, const StringView& nativePath) if (IsLoaded()) return false; PROFILE_CPU(); + PROFILE_MEM(ScriptingCSharp); ZoneText(*assemblyPath, assemblyPath.Length()); Stopwatch stopwatch; @@ -125,7 +138,12 @@ void MAssembly::Unload(bool isReloading) _isLoading = false; _isLoaded = false; _hasCachedClasses = false; +#if USE_NETCORE + ArenaAllocator::ClearDelete(_classes); +#else _classes.ClearDelete(); +#endif + Memory.Free(); Unloaded(this); } @@ -228,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) @@ -242,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 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/ManagedCLR/MEvent.h b/Source/Engine/Scripting/ManagedCLR/MEvent.h index 52573e204..9d8551774 100644 --- a/Source/Engine/Scripting/ManagedCLR/MEvent.h +++ b/Source/Engine/Scripting/ManagedCLR/MEvent.h @@ -15,21 +15,21 @@ 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; - mutable Array _attributes; + mutable Array _attributes; public: #if USE_MONO @@ -42,7 +42,7 @@ public: /// /// Gets the event name. /// - FORCE_INLINE const StringAnsi& GetName() const + FORCE_INLINE StringAnsiView GetName() const { return _name; } @@ -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 49bce9f54..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" /// @@ -17,21 +18,22 @@ 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; mutable int32 _hasCachedAttributes : 1; int32 _isStatic : 1; - mutable Array _attributes; + mutable Array _attributes; public: #if USE_MONO @@ -44,7 +46,7 @@ public: /// /// Gets field name. /// - FORCE_INLINE const StringAnsi& GetName() const + FORCE_INLINE StringAnsiView GetName() const { return _name; } @@ -156,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 989147754..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 @@ -21,15 +22,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; @@ -41,19 +43,18 @@ protected: #endif int32 _isStatic : 1; - mutable Array _attributes; + mutable Array _attributes; public: #if USE_MONO 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 +110,7 @@ public: /// /// Gets the method name. /// - FORCE_INLINE const StringAnsi& GetName() const + FORCE_INLINE StringAnsiView GetName() const { return _name; } @@ -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 3be94eb1a..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" /// @@ -17,21 +18,21 @@ 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; - mutable Array _attributes; + mutable Array _attributes; public: #if USE_MONO @@ -49,7 +50,7 @@ public: /// /// Gets the property name. /// - FORCE_INLINE const StringAnsi& GetName() const + FORCE_INLINE StringAnsiView GetName() const { return _name; } @@ -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/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/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..5b67670e5 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 @@ -182,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. @@ -211,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) { @@ -222,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; @@ -281,6 +282,7 @@ void MCore::UnloadDomain(const StringAnsi& domainName) bool MCore::LoadEngine() { PROFILE_CPU(); + PROFILE_MEM(ScriptingCSharp); // Initialize hostfxr if (InitHostfxr()) @@ -548,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(); @@ -735,6 +743,7 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const if (_hasCachedClasses || !IsLoaded()) return _classes; PROFILE_CPU(); + PROFILE_MEM(ScriptingCSharp); Stopwatch stopwatch; #if TRACY_ENABLE @@ -750,12 +759,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; @@ -796,6 +806,7 @@ void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullnam DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle) { + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); MAssembly* assembly = GetAssembly(assemblyHandle); if (assembly == nullptr) @@ -807,7 +818,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()); @@ -815,7 +826,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 @@ -831,6 +842,7 @@ bool MAssembly::LoadCorlib() if (IsLoaded()) return false; PROFILE_CPU(); + PROFILE_MEM(ScriptingCSharp); #if TRACY_ENABLE const StringAnsiView name("Corlib"); ZoneText(*name, name.Length()); @@ -910,12 +922,18 @@ 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) + , _methods(&parentAssembly->Memory) + , _fields(&parentAssembly->Memory) + , _properties(&parentAssembly->Memory) + , _attributes(&parentAssembly->Memory) + , _events(&parentAssembly->Memory) + , _interfaces(&parentAssembly->Memory) , _hasCachedProperties(false) , _hasCachedFields(false) , _hasCachedMethods(false) @@ -962,6 +980,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; } @@ -977,24 +997,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; @@ -1052,10 +1062,11 @@ 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; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedMethods) return _methods; @@ -1065,10 +1076,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); } @@ -1089,10 +1101,11 @@ MField* MClass::GetField(const char* name) const return nullptr; } -const Array& MClass::GetFields() const +const Array& MClass::GetFields() const { if (_hasCachedFields) return _fields; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedFields) return _fields; @@ -1105,7 +1118,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); } @@ -1115,10 +1128,11 @@ const Array& MClass::GetFields() const return _fields; } -const Array& MClass::GetEvents() const +const Array& MClass::GetEvents() const { if (_hasCachedEvents) return _events; + PROFILE_MEM(ScriptingCSharp); // TODO: implement MEvent in .NET @@ -1137,10 +1151,11 @@ MProperty* MClass::GetProperty(const char* name) const return nullptr; } -const Array& MClass::GetProperties() const +const Array& MClass::GetProperties() const { if (_hasCachedProperties) return _properties; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedProperties) return _properties; @@ -1153,7 +1168,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); } @@ -1163,10 +1178,11 @@ const Array& MClass::GetProperties() const return _properties; } -const Array& MClass::GetInterfaces() const +const Array& MClass::GetInterfaces() const { if (_hasCachedInterfaces) return _interfaces; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedInterfaces) return _interfaces; @@ -1202,10 +1218,11 @@ 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; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1230,10 +1247,11 @@ 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) + , _attributes(&parentClass->GetAssembly()->Memory) { } @@ -1262,7 +1280,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; @@ -1306,8 +1324,9 @@ 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) + , _attributes(&parentClass->GetAssembly()->Memory) { switch (attributes & MFieldAttributes::FieldAccessMask) { @@ -1384,10 +1403,11 @@ 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; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1397,13 +1417,14 @@ 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) + , _attributes(&parentClass->GetAssembly()->Memory) { switch (attributes & MMethodAttributes::MemberAccessMask) { @@ -1431,13 +1452,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; @@ -1450,6 +1473,7 @@ void MMethod::CacheSignature() const ScopeLock lock(BinaryModule::Locker); if (_hasCachedSignature) return; + PROFILE_MEM(ScriptingCSharp); static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType")); static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes")); @@ -1546,10 +1570,11 @@ 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; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1559,20 +1584,31 @@ 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) + , _attributes(&parentClass->GetAssembly()->Memory) { _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; } @@ -1580,9 +1616,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 @@ -1624,10 +1660,11 @@ 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; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; @@ -1658,6 +1695,7 @@ MClass* GetOrCreateClass(MType* typeHandle) { if (!typeHandle) return nullptr; + PROFILE_MEM(ScriptingCSharp); ScopeLock lock(BinaryModule::Locker); MClass* klass; if (!CachedClassHandles.TryGet(typeHandle, klass)) @@ -1667,7 +1705,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()); @@ -1781,9 +1819,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"); @@ -1869,12 +1911,13 @@ void ShutdownHostfxr() { } -void* GetStaticMethodPointer(const String& methodName) +void* GetStaticMethodPointer(StringView methodName) { void* fun; 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); @@ -2236,12 +2279,13 @@ void ShutdownHostfxr() #endif } -void* GetStaticMethodPointer(const String& methodName) +void* GetStaticMethodPointer(StringView methodName) { void* fun; 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 317f4e798..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(); @@ -1539,7 +1544,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 +1667,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 +1820,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 +1993,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 +2123,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..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() { } @@ -358,7 +363,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 +374,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 +391,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 +412,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 +454,7 @@ MObject* MEvent::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MEvent::GetAttributes() const +const Array& MEvent::GetAttributes() const { return _attributes; } @@ -501,7 +506,7 @@ MObject* MField::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MField::GetAttributes() const +const Array& MField::GetAttributes() const { return _attributes; } @@ -556,7 +561,7 @@ MObject* MMethod::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MMethod::GetAttributes() const +const Array& MMethod::GetAttributes() const { return _attributes; } @@ -603,7 +608,7 @@ MObject* MProperty::GetAttribute(const MClass* klass) const return nullptr; } -const Array& MProperty::GetAttributes() const +const Array& MProperty::GetAttributes() const { return _attributes; } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 593092b8f..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" @@ -33,6 +34,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 +175,7 @@ void onEngineUnloading(MAssembly* assembly); bool ScriptingService::Init() { + PROFILE_MEM(Scripting); Stopwatch stopwatch; // Initialize managed runtime @@ -254,30 +257,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 +314,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 +491,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); @@ -608,6 +618,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()); @@ -710,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(); @@ -1034,6 +1046,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 +1129,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/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index ebbfd70e6..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" @@ -16,6 +17,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 +292,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 +434,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 +474,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 +731,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..f7896feae 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; @@ -431,8 +433,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 +446,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh } } +#if 0 // Smooth normals for (int32 z = 1; z < normalsSize.Y - 1; z++) { @@ -466,8 +467,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 +480,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 +525,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; @@ -823,6 +822,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 +1034,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 +1183,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); @@ -1247,7 +1249,13 @@ void TerrainPatch::ClearCache() void TerrainPatch::CacheHeightData() { + if (Heightmap == nullptr) + { + LOG(Error, "Missing heightmap."); + return; + } PROFILE_CPU_NAMED("Terrain.CacheHeightData"); + PROFILE_MEM(LevelTerrain); const TerrainDataUpdateInfo info(this); // Ensure that heightmap data is all loaded @@ -1313,6 +1321,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 +1405,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 +1500,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 +1578,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 +1750,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); @@ -1745,7 +1758,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); @@ -2126,6 +2139,7 @@ void TerrainPatch::UpdatePostManualDeserialization() void TerrainPatch::CreateCollision() { PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); ASSERT(!HasCollision()); if (CreateHeightField()) return; @@ -2241,6 +2255,7 @@ void TerrainPatch::DestroyCollision() void TerrainPatch::CacheDebugLines() { PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); ASSERT(_physicsHeightField); _debugLinesDirty = false; if (!_debugLines) @@ -2322,6 +2337,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 +2394,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 +2445,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 +2543,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& vertexBuffer, Array& indexBuffer) { PROFILE_CPU(); + PROFILE_MEM(LevelTerrain); vertexBuffer.Clear(); indexBuffer.Clear(); 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); } 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/ConcurrentSystemLocker.cpp b/Source/Engine/Threading/ConcurrentSystemLocker.cpp index c8569b119..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]; @@ -18,7 +18,15 @@ 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; + } + + // 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); goto RETRY; } 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() diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index 634f1a884..e90a2e847 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++) { @@ -379,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 57aaf7bb8..bab794728 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(); do @@ -74,6 +75,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()) 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/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/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")); 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/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" 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/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(); } } 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; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index bc7b88647..6b2988b7a 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(); @@ -185,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; @@ -237,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"); 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/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; diff --git a/Source/Engine/Utilities/Screenshot.cpp b/Source/Engine/Utilities/Screenshot.cpp index 85abf19d8..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 @@ -82,6 +83,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 +149,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 +214,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); 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..75950f3aa 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" @@ -16,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 @@ -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; 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/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) { 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)); 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); 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/ProjectTarget.cs b/Source/Tools/Flax.Build/Build/ProjectTarget.cs index b906a9d93..f67139a86 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"); + } if (EngineConfiguration.WithSDL(options)) { options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_SDL"); 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/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 7579c9f4f..9b572d940 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; + /// /// True if SDL support should be enabled. /// 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 ed467c1c6..52ab4df22 100644 --- a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs @@ -1,9 +1,22 @@ // 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 +{ + 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 { @@ -22,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/Mac/MacToolchain.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs index 9a4261383..df42df310 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/Windows/WindowsToolchain.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs index b33ca5e25..5154cdbf9 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs @@ -14,7 +14,13 @@ 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"; + + /// + /// 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/) } } @@ -38,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); } /// @@ -80,6 +81,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 +100,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; } } 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"; } } 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; + } } }