From d5a496751ce06593e5704b15320e521ec84b4bc7 Mon Sep 17 00:00:00 2001 From: Jacob Hilliard Date: Tue, 13 Jul 2021 10:21:56 -0700 Subject: [PATCH 1/7] Visualizer: Implement region search + highlight Signed-off-by: Jacob Hilliard --- .../Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h | 8 +++++--- .../Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl | 10 ++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h index 66be0b9137..4d22458b56 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h @@ -165,9 +165,11 @@ namespace AZ AZStd::vector m_frameEndTicks = { INT64_MIN }; // Main data structure for storing function statistics to be shown in the popup windows. - // For now we default allocate for all regions on the first render frame and then use RegionStatistics.m_draw to determine - // if we should draw the window or not. FIXME(ATOM-15948) this should be changed once RegionStatistics gets heavier. - AZStd::unordered_map m_regionStatisticsMap; + // Uses the group name + region name as a key - just the region name does not suffice since there are collisions (ex. GarbageCollect) + AZStd::unordered_map m_regionStatisticsMap; + + // Filter for highlighting regions on the visualizer + ImGuiTextFilter m_regionHighlightFilter; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index 735dace1c7..81fa440159 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -279,8 +279,8 @@ namespace AZ if (ImGui::BeginChild("Options and Statistics", { 0, 0 }, true)) { ImGui::Columns(3, "Options", true); - ImGui::Text("Frames To Collect:"); - ImGui::SliderInt("", &m_framesToCollect, 10, 10000, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic); + ImGui::SliderInt("Saved Frames", &m_framesToCollect, 10, 10000, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic); + m_regionHighlightFilter.Draw("Find Region"); ImGui::NextColumn(); @@ -522,6 +522,12 @@ namespace AZ inline void ImGuiCpuProfiler::DrawBlock(const TimeRegion& block, u64 targetRow) { + // Don't draw anything if the user is searching for regions and this block doesn't pass the filter + if (!m_regionHighlightFilter.PassFilter(block.m_groupRegionName->m_regionName)) + { + return; + } + float wy = ImGui::GetWindowPos().y - ImGui::GetScrollY(); ImDrawList* drawList = ImGui::GetWindowDrawList(); From 487fc631eca1dd23c83e77ad06eb898ecf567570 Mon Sep 17 00:00:00 2001 From: Jacob Hilliard Date: Mon, 12 Jul 2021 16:47:33 -0700 Subject: [PATCH 2/7] Visualizer: Tabular view of function statistics Current metrics are MTPC, max time, and invocations per frame. The invocations per frame is buggy if switching between samples but I don't know how to fix that in a context-agnostic way (editor vs ASV) - resetting works for now. Signed-off-by: Jacob Hilliard --- .../Include/Atom/Utils/ImGuiCpuProfiler.h | 41 +-- .../Include/Atom/Utils/ImGuiCpuProfiler.inl | 276 +++++++++--------- 2 files changed, 153 insertions(+), 164 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h index 4d22458b56..695ad7e332 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h @@ -31,28 +31,29 @@ namespace AZ AZStd::sys_time_t m_endTick = 0; }; - // Stores data about a region that is agreggated from all collected frames - // Data collection can be toggled on and off through m_record. - struct RegionStatistics + struct TableRow { - float CalcAverageTimeMs() const; void RecordRegion(const AZ::RHI::CachedTimeRegion& region); + double GetAverageInvocationsPerFrame() const; - bool m_draw = false; - bool m_record = true; - u64 m_invocations = 0; - AZStd::sys_time_t m_totalTicks = 0; + static u64 ms_frames; + + AZStd::string m_groupName; + AZStd::string m_regionName; + AZStd::sys_time_t m_maxTicks; + AZStd::sys_time_t m_runningAverageTicks; + u64 m_invocations; }; //! Visual profiler for Cpu statistics. //! It uses ImGui as the library for displaying the Attachments and Heaps. - //! It shows all heaps that are being used by the RHI and how the + //! It shows all heaps that are being used by the RHI and how the FIXME //! resources are allocated in each heap. class ImGuiCpuProfiler : SystemTickBus::Handler { // Region Name -> Array of ThreadRegion entries - using RegionEntryMap = AZStd::map>; + using RegionEntryMap = AZStd::map; // Group Name -> RegionEntryMap using GroupRegionMap = AZStd::map; @@ -81,12 +82,21 @@ namespace AZ // Draw the shared header between the two windows void DrawCommonHeader(); + // Draw the region statstics table in the order specified by the pointers in m_tableData + void DrawTable(); + + // Sort the table by a given column, rearranges the pointers in m_tableData + void SortTable(ImGuiTableSortSpecs* sortSpecs); + // ImGui filter used to filter TimedRegions. ImGuiTextFilter m_timedRegionFilter; - // Saves statistical view data organized by group name -> region name -> regions + // Saves statistical view data organized by group name -> region name -> row data GroupRegionMap m_groupRegionMap; + // Saves pointers to objects in m_groupRegionMap, order reflects table ordering + AZStd::vector m_tableData; + // Pause cpu profiling. The profiler will show the statistics of the last frame before pause bool m_paused = false; @@ -120,9 +130,6 @@ namespace AZ // Draw the "Thread XXXXX" label onto the viewport void DrawThreadLabel(u64 baseRow, AZStd::thread_id threadId); - // Draws all active function statistics windows - void DrawRegionStatistics(); - // Draw the vertical lines separating frames in the timeline void DrawFrameBoundaries(); @@ -164,12 +171,8 @@ namespace AZ // Tracks the frame boundaries AZStd::vector m_frameEndTicks = { INT64_MIN }; - // Main data structure for storing function statistics to be shown in the popup windows. - // Uses the group name + region name as a key - just the region name does not suffice since there are collisions (ex. GarbageCollect) - AZStd::unordered_map m_regionStatisticsMap; - // Filter for highlighting regions on the visualizer - ImGuiTextFilter m_regionHighlightFilter; + ImGuiTextFilter m_visualizerHighlightFilter; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index 81fa440159..bb470f39e8 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -17,11 +17,16 @@ #include #include +#pragma optimize("", off) + +#include "../../../Gems/ImGui/External/ImGui/v1.82/imgui/imgui.h" namespace AZ { namespace Render { + inline u64 TableRow::ms_frames = 0; + namespace CpuProfilerImGuiHelper { // NOTE: Fix build error in case AZStd::thread_id is not of an arithmetic type, and instead a pointer @@ -134,6 +139,92 @@ namespace AZ } } + inline void ImGuiCpuProfiler::DrawTable() + { + const auto flags = + ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable; + if (ImGui::BeginTable("FunctionStatisticsTable", 5, flags)) + { + // Table header setup + ImGui::TableSetupColumn("Group"); + ImGui::TableSetupColumn("Region"); + ImGui::TableSetupColumn("MTPC (ms)"); + ImGui::TableSetupColumn("Max (ms)"); + ImGui::TableSetupColumn("Invocations/frame"); + ImGui::TableHeadersRow(); + ImGui::TableNextColumn(); + + ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs(); + if (sortSpecs && sortSpecs->SpecsDirty) + { + SortTable(sortSpecs); + } + + // Draw all of the rows held in the GroupRegionMap + for (const auto* statistics : m_tableData) + { + if (!m_timedRegionFilter.PassFilter(statistics->m_groupName.c_str()) + && !m_timedRegionFilter.PassFilter(statistics->m_regionName.c_str())) + { + continue; + } + + ImGui::Text(statistics->m_groupName.c_str()); + ImGui::TableNextColumn(); + + ImGui::Text(statistics->m_regionName.c_str()); + ImGui::TableNextColumn(); + + ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_runningAverageTicks)); + ImGui::TableNextColumn(); + + ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_maxTicks)); + ImGui::TableNextColumn(); + + ImGui::Text("%.1f", statistics->GetAverageInvocationsPerFrame()); + ImGui::TableNextColumn(); + } + } + ImGui::EndTable(); + } + + inline void ImGuiCpuProfiler::SortTable(ImGuiTableSortSpecs* sortSpecs) + { + const bool ascending = sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending; + const ImS16 columnToSort = sortSpecs->Specs->ColumnIndex; + + switch (columnToSort) + { + case (0): // Sort by group name + AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ + return ascending ? lhs->m_groupName < rhs->m_groupName : lhs->m_groupName > rhs->m_groupName; + }); + break; + case (1): // Sort by region name + AZStd::sort(m_tableData.begin(), m_tableData.end(),[ascending](const TableRow* lhs, const TableRow* rhs){ + return ascending ? lhs->m_regionName < rhs->m_regionName : lhs->m_regionName > rhs->m_regionName; + }); + break; + case (2): // Sort by average time + AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ + return ascending ? lhs->m_runningAverageTicks < rhs->m_runningAverageTicks + : lhs->m_runningAverageTicks > rhs->m_runningAverageTicks; + }); + break; + case (3): // Sort by max time + AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ + return ascending ? lhs->m_maxTicks < rhs->m_maxTicks : lhs->m_maxTicks > rhs->m_maxTicks; + }); + break; + case (4): // Sort by invocations + AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ + return ascending ? lhs->m_invocations < rhs->m_invocations : lhs->m_invocations > rhs->m_invocations; + }); + break; + } + sortSpecs->SpecsDirty = false; + } + inline void ImGuiCpuProfiler::DrawStatisticsView() { DrawCommonHeader(); @@ -156,62 +247,6 @@ namespace AZ ImGui::NextColumn(); }; - const auto DrawRegionHoverMarker = [this, &ShowTimeInMs](AZStd::vector& entries) - { - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 60.0f); - - for (ThreadRegionEntry& entry : entries) - { - ImGui::Text(CpuProfilerImGuiHelper::TextThreadId(entry.m_threadId.m_id).c_str()); - - const AZStd::sys_time_t elapsed = entry.m_endTick - entry.m_startTick; - ShowTimeInMs(elapsed); - ImGui::Separator(); - } - - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - }; - - const auto ShowRegionRow = - [ticksPerSecond, &DrawRegionHoverMarker, - &ShowTimeInMs](const char* regionLabel, AZStd::vector regions, AZStd::sys_time_t duration) - { - // Draw the region label - ImGui::Text(regionLabel); - ImGui::NextColumn(); - - // Draw the thread count label - AZStd::sys_time_t totalTime = 0; - AZStd::set threads; - for (ThreadRegionEntry& entry : regions) // Find the thread count and total execution time for all threads - { - threads.insert(entry.m_threadId); - totalTime += entry.m_endTick - entry.m_startTick; - } - const AZStd::string threadLabel = AZStd::string::format("Threads: %u", static_cast(threads.size())); - ImGui::Text(threadLabel.c_str()); - DrawRegionHoverMarker(regions); - ImGui::NextColumn(); - - // Draw the overall invocation count - const AZStd::string invocationLabel = AZStd::string::format("Total calls: %u", static_cast(regions.size())); - ImGui::Text(invocationLabel.c_str()); - DrawRegionHoverMarker(regions); - ImGui::NextColumn(); - - // Draw the time labels (max and then total) - const AZStd::string timeLabel = AZStd::string::format( - "%.2f ms max, %.2f ms total", CpuProfilerImGuiHelper::TicksToMs(duration), - CpuProfilerImGuiHelper::TicksToMs(totalTime)); - ImGui::Text(timeLabel.c_str()); - ImGui::NextColumn(); - }; - if (ImGui::BeginChild("Statistics View", { 0, 0 }, true)) { // Set column settings. @@ -229,44 +264,21 @@ namespace AZ ImGui::Separator(); ImGui::Columns(1, "view", false); - m_timedRegionFilter.Draw("TimedRegion Filter"); - - // Draw the timed regions - if (ImGui::BeginChild("TimedRegions")) + m_timedRegionFilter.Draw("Filter"); + ImGui::SameLine(); + if (ImGui::Button("Clear Filter")) { - for (auto& timeRegionMapEntry : m_groupRegionMap) - { - // Draw the regions - if (ImGui::TreeNodeEx(timeRegionMapEntry.first.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Columns(4, "view", false); - ImGui::SetColumnWidth(0, 400.0f); - ImGui::SetColumnWidth(1, 100.0f); - ImGui::SetColumnWidth(2, 150.0f); - ImGui::SetColumnWidth(3, 240.0f); - - for (auto& region : timeRegionMapEntry.second) - { - // Calculate the region with the longest execution time - AZStd::sys_time_t threadExecutionElapsed = 0; - for (ThreadRegionEntry& entry : region.second) - { - const AZStd::sys_time_t elapsed = entry.m_endTick - entry.m_startTick; - threadExecutionElapsed = AZStd::max(threadExecutionElapsed, elapsed); - } - - // Only draw the TimedRegion rows when it passes the filter - if (m_timedRegionFilter.PassFilter(region.first.c_str())) - { - ShowRegionRow(region.first.c_str(), region.second, threadExecutionElapsed); - } - } - ImGui::Columns(1, "view", false); - ImGui::TreePop(); - } - } - ImGui::EndChild(); + m_timedRegionFilter.Clear(); } + ImGui::SameLine(); + if (ImGui::Button("Reset Table")) + { + m_tableData.clear(); + m_groupRegionMap.clear(); + TableRow::ms_frames = 0; + } + + DrawTable(); } } @@ -280,7 +292,7 @@ namespace AZ { ImGui::Columns(3, "Options", true); ImGui::SliderInt("Saved Frames", &m_framesToCollect, 10, 10000, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic); - m_regionHighlightFilter.Draw("Find Region"); + m_visualizerHighlightFilter.Draw("Find Region"); ImGui::NextColumn(); @@ -372,7 +384,6 @@ namespace AZ baseRow += maxDepth + 1; // Next draw loop should start one row down } - DrawRegionStatistics(); DrawFrameBoundaries(); // Draw an invisible button to capture inputs @@ -432,9 +443,6 @@ namespace AZ // view is only holding data from the last frame, the memory overhead is minimal and gives us a faster redraw // compared to if we needed to transform the visualizer's data into the statistical format every frame. - // Clear the statistical view's cached entries - m_groupRegionMap.clear(); - // Get the latest TimeRegionMap const RHI::CpuProfiler::TimeRegionMap& timeRegionMap = RHI::CpuProfiler::Get()->GetTimeRegionMap(); @@ -461,14 +469,15 @@ namespace AZ // Also update the statistical view's data const AZStd::string& groupName = region.m_groupRegionName->m_groupName; - m_groupRegionMap[groupName][regionName].push_back( - { threadId, region.m_startTick, region.m_endTick }); - // Update running statistics if we want to record this region's data - if (m_regionStatisticsMap[region.m_groupRegionName].m_record) + if (!m_groupRegionMap[groupName].contains(regionName)) { - m_regionStatisticsMap[region.m_groupRegionName].RecordRegion(region); + m_groupRegionMap[groupName][regionName].m_groupName = groupName; + m_groupRegionMap[groupName][regionName].m_regionName = regionName; + m_tableData.push_back(&m_groupRegionMap[groupName][regionName]); } + + m_groupRegionMap[groupName][regionName].RecordRegion(region); } } @@ -523,7 +532,7 @@ namespace AZ inline void ImGuiCpuProfiler::DrawBlock(const TimeRegion& block, u64 targetRow) { // Don't draw anything if the user is searching for regions and this block doesn't pass the filter - if (!m_regionHighlightFilter.PassFilter(block.m_groupRegionName->m_regionName)) + if (!m_visualizerHighlightFilter.PassFilter(block.m_groupRegionName->m_regionName)) { return; } @@ -573,13 +582,14 @@ namespace AZ // Tooltip and block highlighting if (ImGui::IsMouseHoveringRect(startPoint, endPoint) && ImGui::IsWindowHovered()) { - // Open function statistics map on click + // Go to the statistics view when a region is clicked if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - const GroupRegionName* key = block.m_groupRegionName; - m_regionStatisticsMap[key].m_draw = true; + m_enableVisualizer = false; + const auto newFilter = AZStd::string(block.m_groupRegionName->m_regionName); + m_timedRegionFilter = ImGuiTextFilter(newFilter.c_str()); + m_timedRegionFilter.Build(); } - // Hovering outline drawList->AddRect(startPoint, endPoint, ImGui::GetColorU32({ 1, 1, 1, 1 }), 0.0, 0, 1.5); @@ -631,31 +641,6 @@ namespace AZ ImGui::GetWindowDrawList()->AddText({ wx + 10, wy + baseRow * RowHeight + 5 }, IM_COL32_WHITE, threadIdText.c_str()); } - inline void ImGuiCpuProfiler::DrawRegionStatistics() - { - for (auto& [groupRegionName, stat] : m_regionStatisticsMap) - { - if (stat.m_draw) - { - ImGui::SetNextWindowSize({300, 340}, ImGuiCond_FirstUseEver); - ImGui::Begin(groupRegionName->m_regionName, &stat.m_draw, 0); - - if (ImGui::Button(stat.m_record ? "Pause" : "Resume")) - { - stat.m_record = !stat.m_record; - } - - ImGui::Text("Invocations: %llu", stat.m_invocations); - ImGui::Text("Average time: %.3f ms", stat.CalcAverageTimeMs()); - - ImGui::Separator(); - - ImGui::ColorPicker4("Region color", &m_regionColorMap[groupRegionName].x); - ImGui::End(); - } - } - } - inline void ImGuiCpuProfiler::DrawFrameBoundaries() { ImDrawList* drawList = ImGui::GetWindowDrawList(); @@ -857,25 +842,26 @@ namespace AZ else { m_frameEndTicks.push_back(AZStd::GetTimeNowTicks()); + TableRow::ms_frames++; } } - // ----- RegionStatistics implementation ----- - - inline float RegionStatistics::CalcAverageTimeMs() const - { - if (m_invocations == 0) - { - return 0.0; - } - const double averageTicks = aznumeric_cast(m_totalTicks) / m_invocations; - return CpuProfilerImGuiHelper::TicksToMs(aznumeric_cast(averageTicks)); - } + // ---- TableRow impl ---- - inline void RegionStatistics::RecordRegion(const AZ::RHI::CachedTimeRegion& region) + inline void TableRow::RecordRegion(const AZ::RHI::CachedTimeRegion& region) { m_invocations++; - m_totalTicks += region.m_endTick - region.m_startTick; + const AZStd::sys_time_t deltaTime = AZStd::abs(region.m_endTick - region.m_startTick); + m_maxTicks = AZStd::max(m_maxTicks, deltaTime); + + // Standard running average algorithm + const auto newMean = m_runningAverageTicks + aznumeric_cast((deltaTime - m_runningAverageTicks) * 1.0 / m_invocations); + m_runningAverageTicks = newMean; + } + + inline double TableRow::GetAverageInvocationsPerFrame() const + { + return 1.0 * m_invocations / ms_frames; } } // namespace Render } // namespace AZ From bd00867fe624303c7f823e0c5551c6762efae858 Mon Sep 17 00:00:00 2001 From: Jacob Hilliard Date: Wed, 21 Jul 2021 11:57:23 -0700 Subject: [PATCH 3/7] Visualizer: Implement thread hovering tooltip Signed-off-by: Jacob Hilliard --- .../Include/Atom/Utils/ImGuiCpuProfiler.h | 12 ++++-- .../Include/Atom/Utils/ImGuiCpuProfiler.inl | 41 +++++++++++++++---- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h index 695ad7e332..d21783a801 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -33,16 +34,17 @@ namespace AZ struct TableRow { - void RecordRegion(const AZ::RHI::CachedTimeRegion& region); + void RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId); double GetAverageInvocationsPerFrame() const; - - static u64 ms_frames; + AZStd::string TableRow::GetExecutingThreadsLabel() const; AZStd::string m_groupName; AZStd::string m_regionName; AZStd::sys_time_t m_maxTicks; AZStd::sys_time_t m_runningAverageTicks; u64 m_invocations; + + AZStd::set m_executingThreads; }; //! Visual profiler for Cpu statistics. @@ -52,6 +54,8 @@ namespace AZ class ImGuiCpuProfiler : SystemTickBus::Handler { + friend struct TableRow; + // Region Name -> Array of ThreadRegion entries using RegionEntryMap = AZStd::map; // Group Name -> RegionEntryMap @@ -79,6 +83,8 @@ namespace AZ static constexpr float MediumFrameTimeLimit = 16.6; // 60 fps static constexpr float HighFrameTimeLimit = 33.3; // 30 fps + static u64 ms_framesActive; + // Draw the shared header between the two windows void DrawCommonHeader(); diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index bb470f39e8..124e48af13 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -25,7 +26,7 @@ namespace AZ { namespace Render { - inline u64 TableRow::ms_frames = 0; + inline u64 ImGuiCpuProfiler::ms_framesActive = 0; namespace CpuProfilerImGuiHelper { @@ -41,6 +42,7 @@ namespace AZ { return AZStd::string::format("Thread: %zu", static_cast(threadId)); } + inline float TicksToMs(AZStd::sys_time_t ticks) { // Note: converting to microseconds integer before converting to milliseconds float @@ -170,6 +172,7 @@ namespace AZ } ImGui::Text(statistics->m_groupName.c_str()); + const ImVec2 topLeftBound = ImGui::GetItemRectMin(); ImGui::TableNextColumn(); ImGui::Text(statistics->m_regionName.c_str()); @@ -182,7 +185,17 @@ namespace AZ ImGui::TableNextColumn(); ImGui::Text("%.1f", statistics->GetAverageInvocationsPerFrame()); + const ImVec2 botRightBound = ImGui::GetItemRectMax(); ImGui::TableNextColumn(); + + // NOTE: we are manually checking the bounds rather than using ImGui::IsItemHovered + Begin/EndGroup because + // ImGui reports incorrect bounds when using Begin/End group in the Tables API. + if (ImGui::IsMouseHoveringRect(topLeftBound, botRightBound, false)) + { + ImGui::BeginTooltip(); + ImGui::Text(statistics->GetExecutingThreadsLabel().c_str()); + ImGui::EndTooltip(); + } } } ImGui::EndTable(); @@ -275,7 +288,7 @@ namespace AZ { m_tableData.clear(); m_groupRegionMap.clear(); - TableRow::ms_frames = 0; + ImGuiCpuProfiler::ms_framesActive = 0; } DrawTable(); @@ -446,8 +459,8 @@ namespace AZ // Get the latest TimeRegionMap const RHI::CpuProfiler::TimeRegionMap& timeRegionMap = RHI::CpuProfiler::Get()->GetTimeRegionMap(); - m_viewportStartTick = INT64_MAX; - m_viewportEndTick = INT64_MIN; + m_viewportStartTick = AZStd::numeric_limits::max(); + m_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) @@ -477,7 +490,7 @@ namespace AZ m_tableData.push_back(&m_groupRegionMap[groupName][regionName]); } - m_groupRegionMap[groupName][regionName].RecordRegion(region); + m_groupRegionMap[groupName][regionName].RecordRegion(region, threadId); } } @@ -842,13 +855,13 @@ namespace AZ else { m_frameEndTicks.push_back(AZStd::GetTimeNowTicks()); - TableRow::ms_frames++; + ImGuiCpuProfiler::ms_framesActive++; } } // ---- TableRow impl ---- - inline void TableRow::RecordRegion(const AZ::RHI::CachedTimeRegion& region) + inline void TableRow::RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId) { m_invocations++; const AZStd::sys_time_t deltaTime = AZStd::abs(region.m_endTick - region.m_startTick); @@ -857,11 +870,23 @@ namespace AZ // Standard running average algorithm const auto newMean = m_runningAverageTicks + aznumeric_cast((deltaTime - m_runningAverageTicks) * 1.0 / m_invocations); m_runningAverageTicks = newMean; + + m_executingThreads.insert(threadId); } inline double TableRow::GetAverageInvocationsPerFrame() const { - return 1.0 * m_invocations / ms_frames; + return 1.0 * m_invocations / ImGuiCpuProfiler::ms_framesActive; + } + + inline AZStd::string TableRow::GetExecutingThreadsLabel() const + { + AZStd::string threadString; + for (const auto& threadId : m_executingThreads) + { + threadString.append(CpuProfilerImGuiHelper::TextThreadId(threadId.m_id) + ", "); + } + return threadString; } } // namespace Render } // namespace AZ From 11a001946fef6536779a1be54d9d8348dd897e88 Mon Sep 17 00:00:00 2001 From: Jacob Hilliard Date: Wed, 21 Jul 2021 16:14:08 -0700 Subject: [PATCH 4/7] Visualizer: Implement total time + cleanup Signed-off-by: Jacob Hilliard --- .../Include/Atom/Utils/ImGuiCpuProfiler.h | 131 +++++++++--------- .../Include/Atom/Utils/ImGuiCpuProfiler.inl | 72 ++++++---- 2 files changed, 114 insertions(+), 89 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h index d21783a801..a4f5763c99 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -25,41 +24,50 @@ namespace AZ namespace Render { - struct ThreadRegionEntry - { - AZStd::thread_id m_threadId; - AZStd::sys_time_t m_startTick = 0; - AZStd::sys_time_t m_endTick = 0; - }; - + //! Stores all the data associated with a row in the table. struct TableRow { + // Update running statistics with new region data void RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId); - double GetAverageInvocationsPerFrame() const; + + void ResetPerFrameStatistics(); + + // Get a string of all threads that this region executed in during the last frame AZStd::string TableRow::GetExecutingThreadsLabel() const; AZStd::string m_groupName; AZStd::string m_regionName; - AZStd::sys_time_t m_maxTicks; - AZStd::sys_time_t m_runningAverageTicks; - u64 m_invocations; + // --- Per frame statistics --- + + u64 m_invocationsLastFrame = 0; + + // NOTE: set over unordered_set so the threads can be shown in increasing order in tooltip. AZStd::set m_executingThreads; + + AZStd::sys_time_t m_lastFrameTotalTicks = 0; + + // Maximum execution time of a region in the last frame. + AZStd::sys_time_t m_maxTicks = 0; + + // --- Aggregate statistics --- + + u64 m_invocationsTotal = 0; + + // Running average of Mean Time Per Call + AZStd::sys_time_t m_runningAverageTicks = 0; }; - //! Visual profiler for Cpu statistics. - //! It uses ImGui as the library for displaying the Attachments and Heaps. - //! It shows all heaps that are being used by the RHI and how the FIXME - //! resources are allocated in each heap. + //! ImGui widget for examining Atom CPU Profiling instrumentation. + //! Offers both a statistical view (with sorting and searching capability) and a visualizer + //! similar to RAD and other profiling tools. class ImGuiCpuProfiler : SystemTickBus::Handler { - friend struct TableRow; - - // Region Name -> Array of ThreadRegion entries - using RegionEntryMap = AZStd::map; - // Group Name -> RegionEntryMap - using GroupRegionMap = AZStd::map; + // Region Name -> statistical view row data + using RegionRowMap = AZStd::map; + // Group Name -> RegionRowMap + using GroupRegionMap = AZStd::map; using TimeRegion = AZ::RHI::CachedTimeRegion; using GroupRegionName = AZ::RHI::CachedTimeRegion::GroupRegionName; @@ -71,63 +79,34 @@ namespace AZ //! Draws the overall CPU profiling window, defaults to the statistical view void Draw(bool& keepDrawing, const AZ::RHI::CpuTimingStatistics& cpuTimingStatistics); - //! Draws the statistical view of the CPU profiling data - void DrawStatisticsView(); - - //! Draws the CPU profiling visualizer in a new window. - void DrawVisualizer(); - private: static constexpr float RowHeight = 50.0; static constexpr int DefaultFramesToCollect = 50; static constexpr float MediumFrameTimeLimit = 16.6; // 60 fps static constexpr float HighFrameTimeLimit = 33.3; // 30 fps - static u64 ms_framesActive; + //! Draws the statistical view of the CPU profiling data. + void DrawStatisticsView(); - // Draw the shared header between the two windows + //! Draws the CPU profiling visualizer. + void DrawVisualizer(); + + // Draw the shared header between the two windows. void DrawCommonHeader(); - // Draw the region statstics table in the order specified by the pointers in m_tableData + // Draw the region statistics table in the order specified by the pointers in m_tableData. void DrawTable(); - // Sort the table by a given column, rearranges the pointers in m_tableData + // Sort the table by a given column, rearranges the pointers in m_tableData. void SortTable(ImGuiTableSortSpecs* sortSpecs); - // ImGui filter used to filter TimedRegions. - ImGuiTextFilter m_timedRegionFilter; - - // Saves statistical view data organized by group name -> region name -> row data - GroupRegionMap m_groupRegionMap; - - // Saves pointers to objects in m_groupRegionMap, order reflects table ordering - AZStd::vector m_tableData; - - // Pause cpu profiling. The profiler will show the statistics of the last frame before pause - bool m_paused = false; - - // Export the profiling data from a single frame to a local file - bool m_captureToFile = false; - - // Toggle between the normal statistical view and the visual profiling view - bool m_enableVisualizer = false; - - // Total frames need to be saved - int m_captureFrameCount = 1; - - AZ::RHI::CpuTimingStatistics m_cpuTimingStatisticsWhenPause; - - AZStd::string m_lastCapturedFilePath; - - // Visualizer methods - // Get the profiling data from the last frame, only called when the profiler is not paused. void CollectFrameData(); // Cull old data from internal storage, only called when profiler is not paused. void CullFrameData(const AZ::RHI::CpuTimingStatistics& currentCpuTimingStatistics); - // Draws a single block onto the timeline + // Draws a single block onto the timeline into the specified row void DrawBlock(const TimeRegion& block, u64 targetRow); // Draw horizontal lines between threads in the timeline @@ -150,14 +129,14 @@ namespace AZ AZStd::sys_time_t GetViewportTickWidth() const; - // Gets the color for a block using the GroupRegionName as a key into the cache - // Generates a random ImU32 if the block does not yet have a color + // Gets the color for a block using the GroupRegionName as a key into the cache. + // Generates a random ImU32 if the block does not yet have a color. ImU32 GetBlockColor(const TimeRegion& block); // System tick bus overrides virtual void OnSystemTick() override; - // Visualizer state + // --- Visualizer Members --- int m_framesToCollect = DefaultFramesToCollect; @@ -179,6 +158,32 @@ namespace AZ // Filter for highlighting regions on the visualizer ImGuiTextFilter m_visualizerHighlightFilter; + + // --- Tabular view members --- + + // ImGui filter used to filter TimedRegions. + ImGuiTextFilter m_timedRegionFilter; + + // Saves statistical view data organized by group name -> region name -> row data + GroupRegionMap m_groupRegionMap; + + // Saves pointers to objects in m_groupRegionMap, order reflects table ordering. + // Non-owning, will be cleared when m_groupRegionMap is cleared. + AZStd::vector m_tableData; + + // Pause cpu profiling. The profiler will show the statistics of the last frame before pause. + bool m_paused = false; + + // Export the profiling data from a single frame to a local file. + bool m_captureToFile = false; + + // Toggle between the normal statistical view and the visual profiling view. + bool m_enableVisualizer = false; + + // Last captured CPU timing statistics + AZ::RHI::CpuTimingStatistics m_cpuTimingStatisticsWhenPause; + + AZStd::string m_lastCapturedFilePath; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index 124e48af13..d89fed449a 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -18,16 +18,11 @@ #include #include -#pragma optimize("", off) - -#include "../../../Gems/ImGui/External/ImGui/v1.82/imgui/imgui.h" namespace AZ { namespace Render { - inline u64 ImGuiCpuProfiler::ms_framesActive = 0; - namespace CpuProfilerImGuiHelper { // NOTE: Fix build error in case AZStd::thread_id is not of an arithmetic type, and instead a pointer @@ -145,14 +140,15 @@ namespace AZ { const auto flags = ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable; - if (ImGui::BeginTable("FunctionStatisticsTable", 5, flags)) + if (ImGui::BeginTable("FunctionStatisticsTable", 6, flags)) { // Table header setup ImGui::TableSetupColumn("Group"); ImGui::TableSetupColumn("Region"); ImGui::TableSetupColumn("MTPC (ms)"); ImGui::TableSetupColumn("Max (ms)"); - ImGui::TableSetupColumn("Invocations/frame"); + ImGui::TableSetupColumn("Invocations"); + ImGui::TableSetupColumn("Total (ms)"); ImGui::TableHeadersRow(); ImGui::TableNextColumn(); @@ -184,13 +180,16 @@ namespace AZ ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_maxTicks)); ImGui::TableNextColumn(); - ImGui::Text("%.1f", statistics->GetAverageInvocationsPerFrame()); + ImGui::Text("%ld", statistics->m_invocationsLastFrame); + ImGui::TableNextColumn(); + + ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_lastFrameTotalTicks)); const ImVec2 botRightBound = ImGui::GetItemRectMax(); ImGui::TableNextColumn(); // NOTE: we are manually checking the bounds rather than using ImGui::IsItemHovered + Begin/EndGroup because // ImGui reports incorrect bounds when using Begin/End group in the Tables API. - if (ImGui::IsMouseHoveringRect(topLeftBound, botRightBound, false)) + if (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(topLeftBound, botRightBound, false)) { ImGui::BeginTooltip(); ImGui::Text(statistics->GetExecutingThreadsLabel().c_str()); @@ -215,23 +214,32 @@ namespace AZ break; case (1): // Sort by region name AZStd::sort(m_tableData.begin(), m_tableData.end(),[ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_regionName < rhs->m_regionName : lhs->m_regionName > rhs->m_regionName; + return ascending ? lhs->m_regionName < rhs->m_regionName + : lhs->m_regionName > rhs->m_regionName; }); break; case (2): // Sort by average time AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ return ascending ? lhs->m_runningAverageTicks < rhs->m_runningAverageTicks - : lhs->m_runningAverageTicks > rhs->m_runningAverageTicks; + : lhs->m_runningAverageTicks > rhs->m_runningAverageTicks; }); break; case (3): // Sort by max time AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_maxTicks < rhs->m_maxTicks : lhs->m_maxTicks > rhs->m_maxTicks; + return ascending ? lhs->m_maxTicks < rhs->m_maxTicks + : lhs->m_maxTicks > rhs->m_maxTicks; }); break; case (4): // Sort by invocations AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_invocations < rhs->m_invocations : lhs->m_invocations > rhs->m_invocations; + return ascending ? lhs->m_invocationsLastFrame < rhs->m_invocationsLastFrame + : lhs->m_invocationsLastFrame > rhs->m_invocationsLastFrame; + }); + break; + case (5): // Sort by total time + AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ + return ascending ? lhs->m_lastFrameTotalTicks < rhs->m_lastFrameTotalTicks + : lhs->m_lastFrameTotalTicks > rhs->m_lastFrameTotalTicks; }); break; } @@ -288,7 +296,6 @@ namespace AZ { m_tableData.clear(); m_groupRegionMap.clear(); - ImGuiCpuProfiler::ms_framesActive = 0; } DrawTable(); @@ -855,7 +862,14 @@ namespace AZ else { m_frameEndTicks.push_back(AZStd::GetTimeNowTicks()); - ImGuiCpuProfiler::ms_framesActive++; + + for (auto& [groupName, regionMap] : m_groupRegionMap) + { + for (auto& [regionName, row] : regionMap) + { + row.ResetPerFrameStatistics(); + } + } } } @@ -863,28 +877,34 @@ namespace AZ inline void TableRow::RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId) { - m_invocations++; - const AZStd::sys_time_t deltaTime = AZStd::abs(region.m_endTick - region.m_startTick); + const AZStd::sys_time_t deltaTime = region.m_endTick - region.m_startTick; + + // Update per frame statistics + m_invocationsLastFrame++; + m_executingThreads.insert(threadId); + m_lastFrameTotalTicks += deltaTime; m_maxTicks = AZStd::max(m_maxTicks, deltaTime); - // Standard running average algorithm - const auto newMean = m_runningAverageTicks + aznumeric_cast((deltaTime - m_runningAverageTicks) * 1.0 / m_invocations); - m_runningAverageTicks = newMean; - - m_executingThreads.insert(threadId); + // Update aggregate statistics + m_runningAverageTicks = + aznumeric_cast((1.0 * (deltaTime + m_invocationsTotal * m_runningAverageTicks)) / (m_invocationsTotal + 1)); + ++m_invocationsTotal; } - inline double TableRow::GetAverageInvocationsPerFrame() const + inline void TableRow::ResetPerFrameStatistics() { - return 1.0 * m_invocations / ImGuiCpuProfiler::ms_framesActive; + m_invocationsLastFrame = 0; + m_executingThreads.clear(); + m_lastFrameTotalTicks = 0; + m_maxTicks = 0; } inline AZStd::string TableRow::GetExecutingThreadsLabel() const { - AZStd::string threadString; + auto threadString = AZStd::string::format("Executed in %zu threads\n", m_executingThreads.size()); for (const auto& threadId : m_executingThreads) { - threadString.append(CpuProfilerImGuiHelper::TextThreadId(threadId.m_id) + ", "); + threadString.append(CpuProfilerImGuiHelper::TextThreadId(threadId.m_id) + "\n"); } return threadString; } From a064cedb59b7680c55d8e46219e82cd0e950a21a Mon Sep 17 00:00:00 2001 From: Jacob Hilliard Date: Tue, 27 Jul 2021 09:40:55 -0700 Subject: [PATCH 5/7] Visualizer: fix clang build error Signed-off-by: Jacob Hilliard --- Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h | 2 +- Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h index a4f5763c99..62b71bdbb8 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h @@ -33,7 +33,7 @@ namespace AZ void ResetPerFrameStatistics(); // Get a string of all threads that this region executed in during the last frame - AZStd::string TableRow::GetExecutingThreadsLabel() const; + AZStd::string GetExecutingThreadsLabel() const; AZStd::string m_groupName; AZStd::string m_regionName; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index d89fed449a..79a2ffd5a0 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -180,7 +180,7 @@ namespace AZ ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_maxTicks)); ImGui::TableNextColumn(); - ImGui::Text("%ld", statistics->m_invocationsLastFrame); + ImGui::Text("%llu", statistics->m_invocationsLastFrame); ImGui::TableNextColumn(); ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_lastFrameTotalTicks)); From a271a85d6efd7ef848c169ad1661c52b19986148 Mon Sep 17 00:00:00 2001 From: Jacob Hilliard Date: Wed, 28 Jul 2021 09:27:55 -0700 Subject: [PATCH 6/7] Visualizer: postfix -> prefix increment Signed-off-by: Jacob Hilliard --- Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index 79a2ffd5a0..90b6c67905 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -880,7 +880,7 @@ namespace AZ const AZStd::sys_time_t deltaTime = region.m_endTick - region.m_startTick; // Update per frame statistics - m_invocationsLastFrame++; + ++m_invocationsLastFrame; m_executingThreads.insert(threadId); m_lastFrameTotalTicks += deltaTime; m_maxTicks = AZStd::max(m_maxTicks, deltaTime); From 5a18b246518d26c1b89cebfb3607e5787564a8d2 Mon Sep 17 00:00:00 2001 From: Jacob Hilliard Date: Thu, 29 Jul 2021 10:08:19 -0700 Subject: [PATCH 7/7] Visualizer: use template functor over hardcoded lambdas Signed-off-by: Jacob Hilliard --- .../Include/Atom/Utils/ImGuiCpuProfiler.h | 14 +++++++++ .../Include/Atom/Utils/ImGuiCpuProfiler.inl | 29 ++++--------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h index 62b71bdbb8..75b7ec9fdd 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h @@ -27,6 +27,20 @@ namespace AZ //! Stores all the data associated with a row in the table. struct TableRow { + template + struct TableRowCompareFunctor + { + TableRowCompareFunctor(T memberPointer, bool isAscending) : m_memberPointer(memberPointer), m_ascending(isAscending){}; + + bool operator()(const TableRow* lhs, const TableRow* rhs) + { + return m_ascending ? lhs->*m_memberPointer < rhs->*m_memberPointer : lhs->*m_memberPointer > rhs->*m_memberPointer; + } + + T m_memberPointer; + bool m_ascending; + }; + // Update running statistics with new region data void RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId); diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index 90b6c67905..ffd7af2f20 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -208,39 +208,22 @@ namespace AZ switch (columnToSort) { case (0): // Sort by group name - AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_groupName < rhs->m_groupName : lhs->m_groupName > rhs->m_groupName; - }); + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_groupName, ascending)); break; case (1): // Sort by region name - AZStd::sort(m_tableData.begin(), m_tableData.end(),[ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_regionName < rhs->m_regionName - : lhs->m_regionName > rhs->m_regionName; - }); + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_regionName, ascending)); break; case (2): // Sort by average time - AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_runningAverageTicks < rhs->m_runningAverageTicks - : lhs->m_runningAverageTicks > rhs->m_runningAverageTicks; - }); + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_runningAverageTicks, ascending)); break; case (3): // Sort by max time - AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_maxTicks < rhs->m_maxTicks - : lhs->m_maxTicks > rhs->m_maxTicks; - }); + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_maxTicks, ascending)); break; case (4): // Sort by invocations - AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_invocationsLastFrame < rhs->m_invocationsLastFrame - : lhs->m_invocationsLastFrame > rhs->m_invocationsLastFrame; - }); + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_invocationsLastFrame, ascending)); break; case (5): // Sort by total time - AZStd::sort(m_tableData.begin(), m_tableData.end(), [ascending](const TableRow* lhs, const TableRow* rhs){ - return ascending ? lhs->m_lastFrameTotalTicks < rhs->m_lastFrameTotalTicks - : lhs->m_lastFrameTotalTicks > rhs->m_lastFrameTotalTicks; - }); + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_lastFrameTotalTicks, ascending)); break; } sortSpecs->SpecsDirty = false;