aboutsummaryrefslogtreecommitdiff
path: root/src/yuzu/render/performance_overlay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu/render/performance_overlay.cpp')
-rw-r--r--src/yuzu/render/performance_overlay.cpp207
1 files changed, 207 insertions, 0 deletions
diff --git a/src/yuzu/render/performance_overlay.cpp b/src/yuzu/render/performance_overlay.cpp
new file mode 100644
index 0000000000..13003dce7b
--- /dev/null
+++ b/src/yuzu/render/performance_overlay.cpp
@@ -0,0 +1,207 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/perf_stats.h"
+#include "performance_overlay.h"
+#include "ui_performance_overlay.h"
+
+#include "main_window.h"
+
+#include <QChart>
+#include <QChartView>
+#include <QGraphicsLayout>
+#include <QLineSeries>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QValueAxis>
+
+// TODO(crueter): Reset samples when user changes turbo, slow, etc.
+PerformanceOverlay::PerformanceOverlay(MainWindow* parent)
+ : QWidget(parent), m_mainWindow{parent}, ui(new Ui::PerformanceOverlay) {
+ ui->setupUi(this);
+
+ setAttribute(Qt::WA_TranslucentBackground);
+ setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);
+ raise();
+
+ // chart setup
+ m_fpsSeries = new QLineSeries(this);
+
+ QPen pen(Qt::red);
+ pen.setWidth(2);
+ m_fpsSeries->setPen(pen);
+
+ m_fpsChart = new QChart;
+ m_fpsChart->addSeries(m_fpsSeries);
+ m_fpsChart->legend()->hide();
+ m_fpsChart->setBackgroundBrush(Qt::black);
+ m_fpsChart->setBackgroundVisible(true);
+ m_fpsChart->layout()->setContentsMargins(2, 2, 2, 2);
+ m_fpsChart->setMargins(QMargins{4, 4, 4, 4});
+
+ // axes
+ m_fpsX = new QValueAxis(this);
+ m_fpsX->setRange(0, NUM_FPS_SAMPLES);
+ m_fpsX->setVisible(false);
+
+ m_fpsY = new QValueAxis(this);
+ m_fpsY->setRange(0, 60);
+ m_fpsY->setLabelFormat(QStringLiteral("%d"));
+ m_fpsY->setLabelsColor(Qt::white);
+
+ QFont axisFont = m_fpsY->labelsFont();
+ axisFont.setPixelSize(10);
+ m_fpsY->setLabelsFont(axisFont);
+ m_fpsY->setTickCount(3);
+
+ // gray-ish label w/ white lines
+ m_fpsY->setLabelsVisible(true);
+ m_fpsY->setGridLineColor(QColor(50, 50, 50));
+ m_fpsY->setLinePenColor(Qt::white);
+
+ m_fpsChart->addAxis(m_fpsX, Qt::AlignBottom);
+ m_fpsChart->addAxis(m_fpsY, Qt::AlignLeft);
+ m_fpsSeries->attachAxis(m_fpsX);
+ m_fpsSeries->attachAxis(m_fpsY);
+
+ // chart view
+ m_fpsChartView = new QChartView(m_fpsChart, this);
+ m_fpsChartView->setRenderHint(QPainter::Antialiasing);
+ m_fpsChartView->setMinimumHeight(100);
+
+ ui->verticalLayout->addWidget(m_fpsChartView, 1);
+
+ // thanks Debian.
+ QFont font = ui->fps->font();
+ font.setWeight(QFont::DemiBold);
+
+ ui->fps->setFont(font);
+ ui->frametime->setFont(font);
+
+ // pos/stats
+ resetPosition(m_mainWindow->pos());
+ connect(parent, &MainWindow::positionChanged, this, &PerformanceOverlay::resetPosition);
+ connect(m_mainWindow, &MainWindow::statsUpdated, this, &PerformanceOverlay::updateStats);
+}
+
+PerformanceOverlay::~PerformanceOverlay() {
+ delete ui;
+}
+
+void PerformanceOverlay::resetPosition(const QPoint& _) {
+ auto pos = m_mainWindow->pos();
+ move(pos.x() + m_offset.x(), pos.y() + m_offset.y());
+}
+
+void PerformanceOverlay::updateStats(const Core::PerfStatsResults& results,
+ const VideoCore::ShaderNotify& shaders) {
+ auto fps = results.average_game_fps;
+ if (!std::isnan(fps)) {
+ // don't sample measurements < 3 fps because they are probably outliers or freezes
+ static constexpr double FPS_SAMPLE_THRESHOLD = 3.0;
+
+ QString fpsText = tr("%1 fps").arg(std::round(fps), 0, 'f', 0);
+ // if (!m_fpsSuffix.isEmpty()) fpsText = fpsText % QStringLiteral(" (%1)").arg(m_fpsSuffix);
+ ui->fps->setText(fpsText);
+
+ // sampling
+ if (fps > FPS_SAMPLE_THRESHOLD) {
+ m_fpsSamples.push_back(fps);
+ m_fpsPoints.push_back(QPointF{m_xPos++, fps});
+ }
+
+ if (m_fpsSamples.size() > NUM_FPS_SAMPLES) {
+ m_fpsSamples.pop_front();
+ m_fpsPoints.pop_front();
+ }
+
+ // For the average only go back 10 samples max
+ if (m_fpsSamples.size() >= 2) {
+ const int back_search = std::min(size_t(10), m_fpsSamples.size() - 1);
+ double sum = std::accumulate(m_fpsSamples.end() - back_search, m_fpsSamples.end(), 0.0);
+ double avg = sum / back_search;
+
+ ui->fps_avg->setText(tr("Avg: %1").arg(avg, 0, 'f', 0));
+ }
+
+ // chart it :)
+ if (!m_fpsPoints.empty()) {
+ auto [min_it, max_it] = std::minmax_element(m_fpsSamples.begin(), m_fpsSamples.end());
+ double min_fps = *min_it;
+ double max_fps = *max_it;
+
+ ui->fps_min->setText(tr("Min: %1").arg(min_fps, 0, 'f', 0));
+ ui->fps_max->setText(tr("Max: %1").arg(max_fps, 0, 'f', 0));
+
+ m_fpsSeries->replace(QList<QPointF>(m_fpsPoints.begin(), m_fpsPoints.end()));
+
+ qreal x_min = std::max(0.0, m_xPos - NUM_FPS_SAMPLES);
+ qreal x_max = std::max(qreal(10), m_xPos);
+ m_fpsX->setRange(x_min, x_max);
+ m_fpsY->setRange(0.0, max_fps);
+ }
+ }
+
+ auto ft = results.frametime;
+ if (!std::isnan(ft)) {
+ // don't sample measurements > 500 ms because they are probably outliers
+ static constexpr double FT_SAMPLE_THRESHOLD = 500.0;
+
+ double ft_ms = results.frametime * 1000.0;
+ ui->frametime->setText(tr("%1 ms").arg(ft_ms, 0, 'f', 2));
+
+ // sampling
+ if (ft_ms <= FT_SAMPLE_THRESHOLD)
+ m_frametimeSamples.push_back(ft_ms);
+
+ if (m_frametimeSamples.size() > NUM_FRAMETIME_SAMPLES)
+ m_frametimeSamples.pop_front();
+
+ if (!m_frametimeSamples.empty()) {
+ auto [min_it, max_it] =
+ std::minmax_element(m_frametimeSamples.begin(), m_frametimeSamples.end());
+ ui->ft_min->setText(tr("Min: %1").arg(*min_it, 0, 'f', 1));
+ ui->ft_max->setText(tr("Max: %1").arg(*max_it, 0, 'f', 1));
+ }
+
+ // For the average only go back 10 samples max
+ if (m_frametimeSamples.size() >= 2) {
+ const int back_search = std::min(size_t(10), m_frametimeSamples.size() - 1);
+ double sum = std::accumulate(m_frametimeSamples.end() - back_search,
+ m_frametimeSamples.end(), 0.0);
+ double avg = sum / back_search;
+
+ ui->ft_avg->setText(tr("Avg: %1").arg(avg, 0, 'f', 1));
+ }
+ }
+}
+
+void PerformanceOverlay::paintEvent(QPaintEvent* event) {
+ QPainter painter(this);
+
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ painter.setBrush(m_background);
+ painter.setPen(Qt::NoPen);
+
+ painter.drawRoundedRect(rect(), 10.0, 10.0);
+}
+
+void PerformanceOverlay::mousePressEvent(QMouseEvent* event) {
+ if (event->button() == Qt::LeftButton) {
+ m_drag_start_pos = event->pos();
+ }
+}
+
+void PerformanceOverlay::mouseMoveEvent(QMouseEvent* event) {
+ // drag
+ if (event->buttons() & Qt::LeftButton) {
+ QPoint new_global_pos = event->globalPosition().toPoint() - m_drag_start_pos;
+ m_offset = new_global_pos - m_mainWindow->pos();
+ move(new_global_pos);
+ }
+}
+
+void PerformanceOverlay::closeEvent(QCloseEvent* event) {
+ emit closed();
+}