Video mirror: frametime + FPS graphs with min/max/avg stats

The mirror renders into the host window, so the host's render frame timing is
the mirror's performance. FrameStats now retains a ~2 s ring of frame-time
samples; the Video mirror panel plots them as a frametime graph (0-33 ms
scale) and an FPS graph (0-144 scale), each with an avg overlay, and prints
avg/min/max for both frametime and FPS underneath.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 15:19:00 +02:00
parent e07a89c869
commit a1b7578165
4 changed files with 107 additions and 4 deletions

View File

@@ -1,5 +1,7 @@
#include "capture_panel.hpp"
#include <cstdio>
#include "imgui.h"
namespace coop
@@ -11,9 +13,9 @@ bool CapturePanel::init(ID3D11Device* device)
return renderer_.init(device);
}
void CapturePanel::draw_ui()
void CapturePanel::draw_ui(const FrameStats& stats)
{
ImGui::SetNextWindowPos(ImVec2(460, 24), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowPos(ImVec2(460, 40), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(360, 0), ImGuiCond_FirstUseEver);
ImGui::Begin("Video mirror");
@@ -48,9 +50,49 @@ void CapturePanel::draw_ui()
capture_.frame_height());
}
draw_perf_graphs(stats);
ImGui::End();
}
void CapturePanel::draw_perf_graphs(const FrameStats& stats)
{
// The mirror renders into this window, so the host's render frame timing is
// the mirror's performance. Plot the recent history and summarise it.
ImGui::SeparatorText("Render performance");
float ms[FrameStats::kHistory] = {};
const int n = stats.copy_frame_ms(ms);
if (n == 0)
{
ImGui::TextDisabled("Gathering samples...");
return;
}
float fps[FrameStats::kHistory] = {};
for (int i = 0; i < n; ++i)
{
fps[i] = ms[i] > 0.0f ? 1000.0f / ms[i] : 0.0f;
}
float min_ms = 0.0f, max_ms = 0.0f, avg_ms = 0.0f;
stats.history_stats(min_ms, max_ms, avg_ms);
const float avg_fps = avg_ms > 0.0f ? 1000.0f / avg_ms : 0.0f;
const float min_fps = max_ms > 0.0f ? 1000.0f / max_ms : 0.0f; // slowest frame -> lowest FPS
const float max_fps = min_ms > 0.0f ? 1000.0f / min_ms : 0.0f;
char overlay[64];
std::snprintf(overlay, sizeof(overlay), "avg %.2f ms", avg_ms);
// Fixed 0..33 ms scale (30 FPS floor) so the line height is meaningful frame
// to frame rather than auto-rescaling.
ImGui::PlotLines("##frametime", ms, n, 0, overlay, 0.0f, 33.0f, ImVec2(-1.0f, 60.0f));
ImGui::Text("Frametime avg %.2f min %.2f max %.2f ms", avg_ms, min_ms, max_ms);
std::snprintf(overlay, sizeof(overlay), "avg %.0f FPS", avg_fps);
ImGui::PlotLines("##fps", fps, n, 0, overlay, 0.0f, 144.0f, ImVec2(-1.0f, 60.0f));
ImGui::Text("FPS avg %.0f min %.0f max %.0f", avg_fps, min_fps, max_fps);
}
void CapturePanel::render(ID3D11DeviceContext* ctx, std::uint32_t dst_w, std::uint32_t dst_h)
{
if (capture_.running())