From 18fcdcd4f4ee4a6a30b03009baf59c56f31db968 Mon Sep 17 00:00:00 2001 From: Scott Romero <24445312+AMZN-ScottR@users.noreply.github.com> Date: Thu, 17 Feb 2022 11:05:45 -0800 Subject: [PATCH] [development] added option to control update frequency in profiler visualizer (#7705) resolves #4365 Increased the initial profiler ImGui window size Updated thread render order so the main thread is always on top Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com --- .../Profiler/Code/Source/ImGuiCpuProfiler.cpp | 68 +++++++++++++++---- Gems/Profiler/Code/Source/ImGuiCpuProfiler.h | 10 ++- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/Gems/Profiler/Code/Source/ImGuiCpuProfiler.cpp b/Gems/Profiler/Code/Source/ImGuiCpuProfiler.cpp index 3e071570bd..c4da3ab45a 100644 --- a/Gems/Profiler/Code/Source/ImGuiCpuProfiler.cpp +++ b/Gems/Profiler/Code/Source/ImGuiCpuProfiler.cpp @@ -24,13 +24,17 @@ #include #include #include -#include namespace Profiler { constexpr AZStd::sys_time_t ProfilerViewEdgePadding = 5000; constexpr size_t InitialCpuTimingStatsAllocation = 8; + constexpr int MinSavableFrameCount = 30; // 1 second @ 30 fps + constexpr int MaxSavableFrameCount = 2000; + + constexpr int MaxUpdateFrequencyMs = 2000; // 2 seconds + namespace CpuProfilerImGuiHelper { float TicksToMs(double ticks) @@ -116,12 +120,18 @@ namespace Profiler } } // namespace CpuProfilerImGuiHelper + ImGuiCpuProfiler::ImGuiCpuProfiler() + { + // thread IDs are hashed internally to unify display across platforms + m_mainThreadId = AZStd::hash{}(AZStd::this_thread::get_id()); + } + void ImGuiCpuProfiler::Draw(bool& keepDrawing) { // Cache the value to detect if it was changed by ImGui(user pressed 'x') const bool cachedShowCpuProfiler = keepDrawing; - const ImVec2 windowSize(900.0f, 600.0f); + const ImVec2 windowSize(1280.0f, 720.0f); ImGui::SetNextWindowSize(windowSize, ImGuiCond_Once); if (ImGui::Begin("CPU Profiler", &keepDrawing, ImGuiWindowFlags_None)) { @@ -491,6 +501,7 @@ namespace Profiler } // -- CPU Visualizer -- + void ImGuiCpuProfiler::DrawVisualizer() { DrawCommonHeader(); @@ -499,9 +510,19 @@ namespace Profiler if (ImGui::BeginChild("Options and Statistics", { 0, 0 }, true)) { ImGui::Columns(3, "Options", true); - ImGui::SliderInt("Saved Frames", &m_framesToCollect, 10, 20000, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic); + + ImGui::SliderInt("Update Freq. (ms)", &m_updateFrequencyMs, 0, MaxUpdateFrequencyMs, "%d", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderInt("Saved Frames", &m_framesToCollect, MinSavableFrameCount, MaxSavableFrameCount, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic); m_visualizerHighlightFilter.Draw("Find Region"); + // estimate the number of frames required to fulfill the update frequency + const AZ::TimeMs deltaMs = AZ::TimeUsToMs(AZ::GetRealTickDeltaTimeUs()); + const int estimatedFrameCountPadding = 5; // padding is necessary to prevent flashes of blank frames + const int estimatedFrameCount = aznumeric_cast(AZ::TimeMs{ m_updateFrequencyMs } / deltaMs) + estimatedFrameCountPadding; + + // bump the number of saved frames to the update frequency estimate to prevent periods of empty data + m_framesToCollect = AZStd::max(m_framesToCollect, estimatedFrameCount); + ImGui::NextColumn(); ImGui::Text("Viewport width: %.3f ms", CpuProfilerImGuiHelper::TicksToMs(GetViewportTickWidth())); @@ -555,24 +576,25 @@ namespace Profiler // Main draw loop AZ::u64 baseRow = 0; - for (const auto& [currentThreadId, singleThreadData] : m_savedData) + + auto drawThreadDataFunc = [&](size_t threadId, const AZStd::vector& threadData) { // Find the first TimeRegion that we should draw auto regionItr = AZStd::lower_bound( - singleThreadData.begin(), singleThreadData.end(), *startTickItr, + threadData.begin(), threadData.end(), *startTickItr, [](const TimeRegion& wrapper, AZStd::sys_time_t target) { return wrapper.m_startTick < target; }); - if (regionItr == singleThreadData.end()) + if (regionItr == threadData.end()) { - continue; + return; } // Draw all of the blocks for a given thread/row AZ::u64 maxDepth = 0; - while (regionItr != singleThreadData.end()) + while (regionItr != threadData.end()) { const TimeRegion& region = *regionItr; @@ -590,10 +612,20 @@ namespace Profiler } // Draw UI details - DrawThreadLabel(baseRow, currentThreadId); + DrawThreadLabel(baseRow, threadId); DrawThreadSeparator(baseRow, maxDepth); baseRow += maxDepth + 1; // Next draw loop should start one row down + }; + + // keep the main thread at the top + drawThreadDataFunc(m_mainThreadId, m_savedData[m_mainThreadId]); + for (const auto& [threadId, threadData] : m_savedData) + { + if (threadId != m_mainThreadId) + { + drawThreadDataFunc(threadId, threadData); + } } DrawFrameBoundaries(); @@ -678,8 +710,8 @@ namespace Profiler // Get the latest TimeRegionMap const CpuProfiler::TimeRegionMap& timeRegionMap = CpuProfiler::Get()->GetTimeRegionMap(); - m_viewportStartTick = AZStd::numeric_limits::max(); - m_viewportEndTick = AZStd::numeric_limits::lowest(); + AZ::s64 viewportStartTick = AZStd::numeric_limits::max(); + AZ::s64 viewportEndTick = AZStd::numeric_limits::lowest(); // Iterate through the entire TimeRegionMap and copy the data since it will get deleted on the next frame for (const auto& [threadId, singleThreadRegionMap] : timeRegionMap) @@ -724,8 +756,8 @@ namespace Profiler }); // Use the latest frame's data as the new bounds of the viewport - m_viewportStartTick = AZStd::min(newVisualizerData.front().m_startTick, m_viewportStartTick); - m_viewportEndTick = AZStd::max(newVisualizerData.back().m_endTick, m_viewportEndTick); + viewportStartTick = AZStd::min(newVisualizerData.front().m_startTick, viewportStartTick); + viewportEndTick = AZStd::max(newVisualizerData.back().m_endTick, viewportEndTick); m_savedRegionCount += newVisualizerData.size(); @@ -734,6 +766,16 @@ namespace Profiler savedDataVec.insert( savedDataVec.end(), AZStd::make_move_iterator(newVisualizerData.begin()), AZStd::make_move_iterator(newVisualizerData.end())); } + + // only update the viewport bounds at the specified frequency + m_currentUpdateTimeMs += AZ::TimeUsToMs(AZ::GetRealTickDeltaTimeUs()); + if (m_currentUpdateTimeMs >= static_cast(m_updateFrequencyMs)) + { + m_currentUpdateTimeMs = AZ::TimeMs{ 0 }; + + m_viewportStartTick = viewportStartTick; + m_viewportEndTick = viewportEndTick; + } } void ImGuiCpuProfiler::CullFrameData() diff --git a/Gems/Profiler/Code/Source/ImGuiCpuProfiler.h b/Gems/Profiler/Code/Source/ImGuiCpuProfiler.h index d5d27632f8..19eae5d639 100644 --- a/Gems/Profiler/Code/Source/ImGuiCpuProfiler.h +++ b/Gems/Profiler/Code/Source/ImGuiCpuProfiler.h @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -92,7 +93,7 @@ namespace Profiler double m_executeDuration = 0; }; - ImGuiCpuProfiler() = default; + ImGuiCpuProfiler(); ~ImGuiCpuProfiler() = default; //! Draws the overall CPU profiling window, defaults to the statistical view @@ -100,7 +101,8 @@ namespace Profiler private: static constexpr float RowHeight = 35.0f; - static constexpr int DefaultFramesToCollect = 50; + static constexpr int DefaultFramesToCollect = 60; // 1 second @ 60 fps + static constexpr int DefaultUpdateFrequencyMs = 1000; // 1 second static constexpr float MediumFrameTimeLimit = 16.6f; // 60 fps static constexpr float HighFrameTimeLimit = 33.3f; // 30 fps @@ -169,6 +171,9 @@ namespace Profiler // --- Visualizer Members --- + int m_updateFrequencyMs = DefaultUpdateFrequencyMs; + AZ::TimeMs m_currentUpdateTimeMs = AZ::TimeMs{ 0 }; + int m_framesToCollect = DefaultFramesToCollect; // Tally of the number of saved profiling events so far @@ -182,6 +187,7 @@ namespace Profiler // note: we use size_t as a proxy for thread_id because native_thread_id_type differs differs from // platform to platform, which causes problems when deserializing saved captures. AZStd::unordered_map> m_savedData; + size_t m_mainThreadId = 0; // Region color cache AZStd::unordered_map m_regionColorMap;