2024-12-29 02:50:39 +00:00
|
|
|
#include "ProfilerWindow.h"
|
|
|
|
#include <imgui.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <string>
|
|
|
|
#include <iostream> // For debug statements
|
2025-01-01 04:31:58 +00:00
|
|
|
#include "Icons.h"
|
2025-01-01 07:36:53 +00:00
|
|
|
#include "Engine/ScopedTimer.h"
|
2024-12-29 02:50:39 +00:00
|
|
|
|
|
|
|
// Constructor
|
|
|
|
ProfilerWindow::ProfilerWindow()
|
|
|
|
: m_UpdateInterval(0.1) // Set update interval to 0.1 seconds
|
|
|
|
{
|
|
|
|
// Initialize m_LastUpdateTime to force an immediate update on the first frame
|
|
|
|
m_LastUpdateTime = std::chrono::steady_clock::now() - std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(m_UpdateInterval));
|
|
|
|
}
|
|
|
|
|
2024-12-31 08:40:23 +00:00
|
|
|
std::vector<float> ProfilerWindow::ExponentialMovingAverage(const std::deque<double> &data, float alpha)
|
|
|
|
{
|
2024-12-30 04:25:16 +00:00
|
|
|
std::vector<float> ema;
|
|
|
|
ema.reserve(data.size());
|
|
|
|
float prev = 0.0f;
|
2024-12-31 08:40:23 +00:00
|
|
|
for (const auto &val : data)
|
|
|
|
{
|
2024-12-30 04:25:16 +00:00
|
|
|
prev = alpha * static_cast<float>(val) + (1.0f - alpha) * prev;
|
|
|
|
ema.push_back(prev);
|
2024-12-29 03:20:11 +00:00
|
|
|
}
|
2024-12-30 04:25:16 +00:00
|
|
|
return ema;
|
2024-12-29 03:20:11 +00:00
|
|
|
}
|
|
|
|
|
2024-12-31 08:40:23 +00:00
|
|
|
void ProfilerWindow::UpdateHistory(const std::unordered_map<std::string, ProfileResult> &data, double totalFrameTime)
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
|
|
|
// Update total frame time history
|
|
|
|
m_TotalFrameTimeHistory.push_back(totalFrameTime);
|
|
|
|
if (m_TotalFrameTimeHistory.size() > MaxFrameHistory)
|
|
|
|
m_TotalFrameTimeHistory.pop_front();
|
|
|
|
|
|
|
|
// Update each function's profiling history
|
2024-12-31 08:40:23 +00:00
|
|
|
for (const auto &[name, result] : data)
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
2024-12-31 08:40:23 +00:00
|
|
|
auto &history = m_ProfileHistories[name];
|
2024-12-29 02:50:39 +00:00
|
|
|
|
|
|
|
// Update total time history
|
|
|
|
history.totalTimeHistory.push_back(result.TotalTime);
|
|
|
|
if (history.totalTimeHistory.size() > ProfileHistory::MaxHistory)
|
|
|
|
history.totalTimeHistory.pop_front();
|
|
|
|
|
|
|
|
// Update average time history
|
|
|
|
double average = result.CallCount > 0 ? result.TotalTime / result.CallCount : 0.0;
|
|
|
|
history.averageTimeHistory.push_back(average);
|
|
|
|
if (history.averageTimeHistory.size() > ProfileHistory::MaxHistory)
|
|
|
|
history.averageTimeHistory.pop_front();
|
2024-12-30 04:25:16 +00:00
|
|
|
|
|
|
|
// Update call count history
|
|
|
|
history.callCountHistory.push_back(result.CallCount);
|
|
|
|
if (history.callCountHistory.size() > ProfileHistory::MaxHistory)
|
|
|
|
history.callCountHistory.pop_front();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that functions not present in the current frame retain their last TotalTime and AverageTime
|
2024-12-31 08:40:23 +00:00
|
|
|
for (auto &[name, history] : m_ProfileHistories)
|
2024-12-30 04:25:16 +00:00
|
|
|
{
|
|
|
|
if (data.find(name) == data.end())
|
|
|
|
{
|
|
|
|
// Retain last TotalTime and AverageTime by pushing back the last value again
|
|
|
|
if (!history.totalTimeHistory.empty())
|
|
|
|
history.totalTimeHistory.push_back(history.totalTimeHistory.back());
|
|
|
|
else
|
|
|
|
history.totalTimeHistory.push_back(0.0);
|
|
|
|
|
|
|
|
if (!history.averageTimeHistory.empty())
|
|
|
|
history.averageTimeHistory.push_back(history.averageTimeHistory.back());
|
|
|
|
else
|
|
|
|
history.averageTimeHistory.push_back(0.0);
|
|
|
|
|
|
|
|
// Update call count history with zero for this frame
|
|
|
|
history.callCountHistory.push_back(0);
|
|
|
|
|
|
|
|
// Maintain history sizes
|
|
|
|
if (history.totalTimeHistory.size() > ProfileHistory::MaxHistory)
|
|
|
|
history.totalTimeHistory.pop_front();
|
|
|
|
if (history.averageTimeHistory.size() > ProfileHistory::MaxHistory)
|
|
|
|
history.averageTimeHistory.pop_front();
|
|
|
|
if (history.callCountHistory.size() > ProfileHistory::MaxHistory)
|
|
|
|
history.callCountHistory.pop_front();
|
|
|
|
}
|
2024-12-29 02:50:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfilerWindow::Show()
|
|
|
|
{
|
2025-01-01 07:36:53 +00:00
|
|
|
SCOPE_TIMER("ProfilerWindow::Show");
|
|
|
|
|
2024-12-29 02:50:39 +00:00
|
|
|
// Check if it's time to update the profiler data
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
|
|
std::chrono::duration<double> elapsed = now - m_LastUpdateTime;
|
|
|
|
|
|
|
|
bool shouldUpdate = false;
|
|
|
|
if (elapsed.count() >= m_UpdateInterval)
|
|
|
|
{
|
|
|
|
shouldUpdate = true;
|
|
|
|
m_LastUpdateTime = now;
|
|
|
|
}
|
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Begin ImGui window with improved styling
|
2025-01-01 07:36:53 +00:00
|
|
|
ImGui::Begin(ICON_FA_GAUGE " Profiler", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_HorizontalScrollbar);
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-31 08:40:23 +00:00
|
|
|
const auto &data = Profiler::Get().GetLastFrameData();
|
2024-12-29 02:50:39 +00:00
|
|
|
|
|
|
|
if (data.empty())
|
|
|
|
{
|
|
|
|
ImGui::Text("No profiling data available.");
|
|
|
|
ImGui::End();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldUpdate)
|
|
|
|
{
|
|
|
|
// Calculate total frame time
|
|
|
|
double totalFrameTime = 0.0;
|
2024-12-31 08:40:23 +00:00
|
|
|
for (const auto &[name, result] : data)
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
|
|
|
totalFrameTime += result.TotalTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update history data
|
|
|
|
UpdateHistory(data, totalFrameTime);
|
|
|
|
|
|
|
|
// Reset profiling data for the next interval
|
|
|
|
Profiler::Get().EndFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render profiling data table
|
2024-12-30 04:25:16 +00:00
|
|
|
RenderTable();
|
2024-12-29 02:50:39 +00:00
|
|
|
|
|
|
|
// Render profiling graphs
|
2024-12-30 04:25:16 +00:00
|
|
|
RenderGraphs();
|
2024-12-29 02:50:39 +00:00
|
|
|
|
|
|
|
// Display total frame time (from the last update)
|
|
|
|
if (!m_TotalFrameTimeHistory.empty())
|
|
|
|
{
|
|
|
|
double lastTotalFrameTime = m_TotalFrameTimeHistory.back();
|
|
|
|
ImGui::Separator();
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::TextColored(ImVec4(0.4f, 0.7f, 0.0f, 1.0f), "Total Frame Time: %.3f µs", lastTotalFrameTime);
|
2024-12-29 02:50:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::End();
|
|
|
|
}
|
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
void ProfilerWindow::RenderTable()
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
2024-12-30 04:25:16 +00:00
|
|
|
// Collect all profiling histories
|
|
|
|
std::vector<std::pair<std::string, ProfileHistory>> allData(m_ProfileHistories.begin(), m_ProfileHistories.end());
|
|
|
|
|
|
|
|
// Sort functions by last Total Time descending
|
|
|
|
std::sort(allData.begin(), allData.end(),
|
2024-12-31 08:40:23 +00:00
|
|
|
[](const std::pair<std::string, ProfileHistory> &a, const std::pair<std::string, ProfileHistory> &b) -> bool
|
|
|
|
{
|
|
|
|
return a.second.totalTimeHistory.back() > b.second.totalTimeHistory.back();
|
|
|
|
});
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Add a filter input with enhanced styling
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 2));
|
2024-12-29 02:50:39 +00:00
|
|
|
static char filterBuffer[128] = "";
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::InputTextWithHint("##Filter", "Filter functions...", filterBuffer, IM_ARRAYSIZE(filterBuffer));
|
|
|
|
ImGui::PopStyleVar();
|
2024-12-29 02:50:39 +00:00
|
|
|
|
|
|
|
// Convert filter to string
|
|
|
|
std::string filterStr = filterBuffer;
|
|
|
|
|
|
|
|
// Filtered data
|
2024-12-30 04:25:16 +00:00
|
|
|
std::vector<std::pair<std::string, ProfileHistory>> filteredData;
|
2024-12-31 08:40:23 +00:00
|
|
|
for (const auto &[name, history] : allData)
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
|
|
|
if (filterStr.empty() || name.find(filterStr) != std::string::npos)
|
2024-12-30 04:25:16 +00:00
|
|
|
filteredData.emplace_back(name, history);
|
2024-12-29 02:50:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Define threshold for highlighting (e.g., 1000 µs)
|
|
|
|
const double highlightThreshold = 1000.0;
|
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Improved table with sorting indicators and better aesthetics
|
|
|
|
if (ImGui::BeginTable("ProfilerTable", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 300)))
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
2024-12-30 04:25:16 +00:00
|
|
|
// Set up columns with sortable headers
|
2024-12-29 02:50:39 +00:00
|
|
|
ImGui::TableSetupColumn("Function", ImGuiTableColumnFlags_None);
|
|
|
|
ImGui::TableSetupColumn("Total Time (µs)", ImGuiTableColumnFlags_None);
|
|
|
|
ImGui::TableSetupColumn("Average Time (µs)", ImGuiTableColumnFlags_None);
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::TableSetupColumn("Calls (This Frame)", ImGuiTableColumnFlags_None);
|
2024-12-29 02:50:39 +00:00
|
|
|
ImGui::TableHeadersRow();
|
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Alternate row colors for better readability
|
|
|
|
bool rowBg = false;
|
|
|
|
|
2024-12-31 08:40:23 +00:00
|
|
|
for (const auto &[name, history] : filteredData)
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
|
|
|
ImGui::TableNextRow();
|
2024-12-30 04:25:16 +00:00
|
|
|
rowBg = !rowBg;
|
|
|
|
if (rowBg)
|
|
|
|
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImColor(0.1f, 0.1f, 0.1f, 1.0f));
|
2024-12-29 02:50:39 +00:00
|
|
|
|
|
|
|
// Function Name with tooltip
|
|
|
|
ImGui::TableSetColumnIndex(0);
|
|
|
|
ImGui::TextUnformatted(name.c_str());
|
|
|
|
if (ImGui::IsItemHovered())
|
|
|
|
{
|
|
|
|
ImGui::BeginTooltip();
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "Function: %s", name.c_str());
|
|
|
|
ImGui::Text("Total Time: %.3f µs", history.totalTimeHistory.back());
|
|
|
|
ImGui::Text("Average Time: %.3f µs", history.averageTimeHistory.back());
|
|
|
|
ImGui::Text("Call Count (this frame): %d", history.callCountHistory.back());
|
2024-12-29 02:50:39 +00:00
|
|
|
ImGui::EndTooltip();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Total Time with color coding
|
|
|
|
ImGui::TableSetColumnIndex(1);
|
2024-12-30 04:25:16 +00:00
|
|
|
if (history.totalTimeHistory.back() > highlightThreshold)
|
|
|
|
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%.3f", history.totalTimeHistory.back());
|
2024-12-29 02:50:39 +00:00
|
|
|
else
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::Text("%.3f", history.totalTimeHistory.back());
|
2024-12-29 02:50:39 +00:00
|
|
|
|
|
|
|
// Average Time
|
|
|
|
ImGui::TableSetColumnIndex(2);
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::Text("%.3f", history.averageTimeHistory.back());
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Call Count (This Frame)
|
2024-12-29 02:50:39 +00:00
|
|
|
ImGui::TableSetColumnIndex(3);
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::Text("%d", history.callCountHistory.back());
|
2024-12-29 02:50:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::EndTable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProfilerWindow::RenderGraphs()
|
|
|
|
{
|
|
|
|
ImGui::Separator();
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::TextColored(ImVec4(0.0f, 0.7f, 0.9f, 1.0f), "Profiling Graphs (Top 5 Functions)");
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Collect and sort functions by last Total Time descending
|
|
|
|
std::vector<std::pair<std::string, ProfileHistory>> sortedData(m_ProfileHistories.begin(), m_ProfileHistories.end());
|
2024-12-29 02:50:39 +00:00
|
|
|
std::sort(sortedData.begin(), sortedData.end(),
|
2024-12-31 08:40:23 +00:00
|
|
|
[](const std::pair<std::string, ProfileHistory> &a, const std::pair<std::string, ProfileHistory> &b) -> bool
|
|
|
|
{
|
|
|
|
return a.second.totalTimeHistory.back() > b.second.totalTimeHistory.back();
|
|
|
|
});
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-29 03:20:11 +00:00
|
|
|
size_t displayCount = std::min<size_t>(5, sortedData.size()); // Limit to top 5 functions
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Prepare data for the unified plot with EMA smoothing
|
2024-12-29 03:20:11 +00:00
|
|
|
std::vector<std::vector<float>> plotData(displayCount);
|
|
|
|
std::vector<std::string> functionNames;
|
|
|
|
|
|
|
|
float alpha = 0.2f; // Smoothing factor for EMA
|
2024-12-29 02:50:39 +00:00
|
|
|
for (size_t i = 0; i < displayCount; ++i)
|
|
|
|
{
|
2024-12-31 08:40:23 +00:00
|
|
|
const auto &[name, history] = sortedData[i];
|
2024-12-29 03:20:11 +00:00
|
|
|
functionNames.push_back(name);
|
|
|
|
|
|
|
|
// Smooth each function's data using EMA
|
|
|
|
plotData[i] = ExponentialMovingAverage(history.totalTimeHistory, alpha);
|
|
|
|
}
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-29 03:20:11 +00:00
|
|
|
// Find the longest data series and the maximum value for normalization
|
|
|
|
size_t maxHistorySize = 0;
|
|
|
|
float maxValue = 0.0f;
|
2024-12-31 08:40:23 +00:00
|
|
|
for (const auto &series : plotData)
|
2024-12-29 03:20:11 +00:00
|
|
|
{
|
|
|
|
if (!series.empty())
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
2024-12-29 03:20:11 +00:00
|
|
|
maxHistorySize = std::max(maxHistorySize, series.size());
|
|
|
|
maxValue = std::max(maxValue, *std::max_element(series.begin(), series.end()));
|
2024-12-29 02:50:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-29 03:20:11 +00:00
|
|
|
// Prepare the combined graph
|
|
|
|
if (maxHistorySize > 0)
|
2024-12-29 02:50:39 +00:00
|
|
|
{
|
2024-12-29 03:20:11 +00:00
|
|
|
ImVec2 graphSize = ImVec2(0, 200); // Graph dimensions
|
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Begin child region for better layout control
|
|
|
|
ImGui::BeginChild("GraphChild", graphSize, false, ImGuiWindowFlags_NoScrollbar);
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
// Plot each function's history as separate lines with unique colors
|
|
|
|
for (size_t i = 0; i < displayCount; ++i)
|
|
|
|
{
|
|
|
|
ImU32 color = ImColor::HSV(static_cast<float>(i) / displayCount, 0.6f, 0.9f);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_PlotLines, color);
|
|
|
|
ImGui::PlotLines(
|
|
|
|
functionNames[i].c_str(),
|
|
|
|
plotData[i].data(),
|
|
|
|
static_cast<int>(plotData[i].size()),
|
|
|
|
0,
|
|
|
|
nullptr,
|
|
|
|
0.0f,
|
|
|
|
static_cast<float>(maxValue) * 1.1f, // Add some padding to the max value
|
2024-12-31 08:40:23 +00:00
|
|
|
ImVec2(0, 100));
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::PopStyleColor();
|
|
|
|
}
|
2024-12-29 02:50:39 +00:00
|
|
|
|
2024-12-30 04:25:16 +00:00
|
|
|
ImGui::EndChild();
|
2024-12-29 03:20:11 +00:00
|
|
|
|
|
|
|
// Add a legend for the lines
|
|
|
|
ImGui::Separator();
|
|
|
|
for (size_t i = 0; i < functionNames.size(); ++i)
|
|
|
|
{
|
2024-12-30 04:25:16 +00:00
|
|
|
ImVec4 lineColor = ImColor::HSV(static_cast<float>(i) / displayCount, 0.6f, 0.9f);
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::ColorButton(("##Color" + std::to_string(i)).c_str(), lineColor, ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop | ImGuiColorEditFlags_NoBorder, ImVec2(10, 10));
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::TextUnformatted(functionNames[i].c_str());
|
2024-12-29 03:20:11 +00:00
|
|
|
}
|
2024-12-29 02:50:39 +00:00
|
|
|
}
|
2024-12-30 04:25:16 +00:00
|
|
|
}
|