Visualizer: Implement total time + cleanup
Signed-off-by: Jacob Hilliard <jhlliar@amazon.com>
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
#include <AzCore/Math/Random.h>
|
||||
#include <AzCore/std/containers/set.h>
|
||||
|
||||
#include <Atom/RHI.Reflect/CpuTimingStatistics.h>
|
||||
#include <Atom/RHI/CpuProfiler.h>
|
||||
@@ -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<AZStd::thread_id> 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<AZStd::string, TableRow>;
|
||||
// Group Name -> RegionEntryMap
|
||||
using GroupRegionMap = AZStd::map<AZStd::string, RegionEntryMap>;
|
||||
// Region Name -> statistical view row data
|
||||
using RegionRowMap = AZStd::map<AZStd::string, TableRow>;
|
||||
// Group Name -> RegionRowMap
|
||||
using GroupRegionMap = AZStd::map<AZStd::string, RegionRowMap>;
|
||||
|
||||
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<TableRow*> 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<TableRow*> 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
|
||||
|
||||
@@ -18,16 +18,11 @@
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzCore/std/time.h>
|
||||
|
||||
#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<AZStd::sys_time_t>((deltaTime - m_runningAverageTicks) * 1.0 / m_invocations);
|
||||
m_runningAverageTicks = newMean;
|
||||
|
||||
m_executingThreads.insert(threadId);
|
||||
// Update aggregate statistics
|
||||
m_runningAverageTicks =
|
||||
aznumeric_cast<AZStd::sys_time_t>((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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user