#include "PerformanceWindow.h" #include "imgui.h" #include // for std::max #include // for FLT_MAX #include "Engine/ThemeManager.h" #include "Engine/ScopedTimer.h" extern int LoadedAssets; extern int g_GPU_Triangles_drawn_to_screen; extern int g_GPU_Draw_Calls; const char *polygonModeOptions[] = {"Fill", "Wireframe", "Points"}; static int polygonMode = 0; // Initialize static members int PerformanceWindow::m_OpenGLCallCount = 0; int PerformanceWindow::m_TriangleCount = 0; // We'll store up to 60 data points for each stat. static float s_FpsHistory[60] = {0.0f}; static float s_MsHistory[60] = {0.0f}; static float s_CallsHistory[60] = {0.0f}; static float s_TriangleHistory[60] = {0.0f}; // Current dynamic max scale for FPS and ms static float s_FpsScale = 120.0f; // default starting scale for FPS static float s_MsScale = 25.0f; // default starting scale for ms // Push new value into history, shifting old values left static void PushValueToHistory(float* historyArray, int historySize, float newValue) { for (int i = 0; i < historySize - 1; i++) { historyArray[i] = historyArray[i + 1]; } historyArray[historySize - 1] = newValue; } // We'll track when we last pushed data to our history. static double s_LastPushTime = 0.0; // We'll also track when we last updated the scale static double s_LastScaleUpdate = 0.0; void PerformanceWindow::UpdatePerformanceStats(int newCallCount, int newTriangleCount) { m_OpenGLCallCount = newCallCount; m_TriangleCount = newTriangleCount; // Reset GPU counters each frame, or each time you show g_GPU_Triangles_drawn_to_screen = 0; g_GPU_Draw_Calls = 0; } void PerformanceWindow::Show(float fps, float ms) { SCOPE_TIMER("PerformanceWindow::Show"); // 1) Get current time from ImGui's internal clock double currentTime = ImGui::GetTime(); // 2) If at least 0.05s has passed, push new data (~20 times a second) if ((currentTime - s_LastPushTime) >= 0.05) { s_LastPushTime = currentTime; // Push new values into our history arrays PushValueToHistory(s_FpsHistory, 60, fps); PushValueToHistory(s_MsHistory, 60, ms); PushValueToHistory(s_CallsHistory, 60, (float)m_OpenGLCallCount); PushValueToHistory(s_TriangleHistory, 60, (float)m_TriangleCount); } // We'll calculate a fallback max for calls static float callsMax = 300.0f; // 3) Every 1 second, recalculate the max scale for FPS, ms, and calls if ((currentTime - s_LastScaleUpdate) >= 1.0) { s_LastScaleUpdate = currentTime; // Recompute s_FpsScale float maxFps = 0.0f; for (int i = 0; i < 60; i++) maxFps = std::max(maxFps, s_FpsHistory[i]); maxFps = std::max(1.0f, maxFps * 1.15f); // add ~15% headroom s_FpsScale = maxFps; // Recompute s_MsScale float maxMs = 0.0f; for (int i = 0; i < 60; i++) maxMs = std::max(maxMs, s_MsHistory[i]); maxMs = std::max(1.0f, maxMs * 1.15f); s_MsScale = maxMs; // Recompute callsMax float localMax = 0.0f; for (int i = 0; i < 60; i++) localMax = std::max(localMax, s_CallsHistory[i]); if (localMax > 0.0f) callsMax = localMax * 1.2f; // 20% headroom else callsMax = 300.0f; // default } // Optional style adjustments ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); // Optionally make the window auto-resize or set constraints: // ImGui::SetNextWindowSizeConstraints(ImVec2(350, 300), ImVec2(FLT_MAX, FLT_MAX)); // ImGui::Begin("Performance##performance", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::Begin("Performance##performance"); // A color-coded main header #ifdef DEBUG ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Performance Stats (DEBUG)"); #else ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Performance Stats"); #endif ImGui::Separator(); ImGui::Spacing(); // Show current FPS / ms in color { // Line 1: FPS (green) + MS (teal) ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "FPS: %.1f", fps); ImGui::SameLine(); ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.8f, 1.0f), "| ms: %.3f", ms); } // Collapsible header for the performance graphs if (ImGui::CollapsingHeader("Performance Graphs", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Spacing(); // --------- FPS & MS side by side ---------- ImGui::BeginGroup(); // left group { ImGui::Text("FPS"); ImGui::PushStyleColor(ImGuiCol_PlotLines, IM_COL32(0, 255, 0, 255)); // green ImGui::PushStyleColor(ImGuiCol_PlotLinesHovered, IM_COL32(255, 0, 0, 255)); // red on hover ImGui::PlotLines("##FPS", // hidden label s_FpsHistory, IM_ARRAYSIZE(s_FpsHistory), 0, // offset nullptr, // overlay 0.0f, s_FpsScale, ImVec2(150, 60)); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Frames per second over time"); ImGui::EndTooltip(); } ImGui::PopStyleColor(2); } ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); // right group { ImGui::Text("ms/frame"); ImGui::PlotHistogram("##ms/frame", s_MsHistory, IM_ARRAYSIZE(s_MsHistory), 0, nullptr, 0.0f, s_MsScale, ImVec2(150, 60)); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Frame time in milliseconds"); ImGui::EndTooltip(); } } ImGui::EndGroup(); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // --------- OpenGL Calls & Indices ----------- // 1) GL Calls ImGui::Text("OpenGL Calls: %d", m_OpenGLCallCount); ImGui::PlotLines("##GL Calls", s_CallsHistory, IM_ARRAYSIZE(s_CallsHistory), 0, // offset nullptr, // overlay text 0.0f, callsMax, ImVec2(-1, 50)); // auto-fill width, fixed height=50 if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Number of GL draw calls per frame"); ImGui::EndTooltip(); } ImGui::Spacing(); // 2) Indices ImGui::Text("Indices: %d", m_TriangleCount); // For Indices, we use a histogram. We could also do dynamic range like callsMax. float indexHistMax = std::max(1.0f, (float)m_TriangleCount * 2.5f); ImGui::PlotHistogram("##Indices", s_TriangleHistory, IM_ARRAYSIZE(s_TriangleHistory), 0, nullptr, 0.0f, indexHistMax, ImVec2(-1, 50)); // auto-size width, height=50 if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Number of indices being rendered per frame"); ImGui::EndTooltip(); } } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // -------------- Asset Count -------------- { ImGui::Text("Loaded Assets: "); ImGui::SameLine(); ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%d", LoadedAssets); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // -------------- Theme Combo Box -------------- { const char* options[] = {"Bootstrap", "Duck Red", "Windark", "Deep Dark", "Tesseract Black"}; static int current_option = -1; const char* preview_value = (current_option >= 0 && current_option < IM_ARRAYSIZE(options)) ? options[current_option] : "Select an option"; ImGui::Text("Theme: "); ImGui::SameLine(); if (ImGui::BeginCombo("##ThemeCombo", preview_value)) { for (int n = 0; n < IM_ARRAYSIZE(options); n++) { bool is_selected = (current_option == n); if (ImGui::Selectable(options[n], is_selected)) { current_option = n; // Switch theme ThemeManager_ChangeTheme(n); } if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } } ImGui::Spacing(); // -------------- Polygon Mode Combo Box -------------- { ImGui::Text("Polygon Mode: "); ImGui::SameLine(); if (ImGui::Combo("##PolygonMode", &polygonMode, polygonModeOptions, IM_ARRAYSIZE(polygonModeOptions))) { switch (polygonMode) { case 0: // Fill glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); break; case 1: // Wireframe glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); break; case 2: // Points glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); break; default: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); break; } } } ImGui::End(); // End of "Performance##performance" // Pop the style var ImGui::PopStyleVar(); }