mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-19 18:53:32 +00:00
Merge branch 'performance-overlay-enhancements' into 'master'
feat: enhance performance overlay with frame graph and draggable UI See merge request citron/rewrite!42
This commit is contained in:
@@ -224,6 +224,8 @@ add_executable(citron
|
||||
util/overlay_dialog.cpp
|
||||
util/overlay_dialog.h
|
||||
util/overlay_dialog.ui
|
||||
util/performance_overlay.cpp
|
||||
util/performance_overlay.h
|
||||
util/sequence_dialog/sequence_dialog.cpp
|
||||
util/sequence_dialog/sequence_dialog.h
|
||||
util/url_request_interceptor.cpp
|
||||
|
||||
@@ -165,6 +165,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include "citron/updater/updater_dialog.h"
|
||||
#include "citron/updater/updater_service.h"
|
||||
#include "citron/util/clickable_label.h"
|
||||
#include "citron/util/performance_overlay.h"
|
||||
#include "citron/vk_device_info.h"
|
||||
|
||||
#ifdef CITRON_CRASH_DUMPS
|
||||
@@ -1075,6 +1076,10 @@ void GMainWindow::InitializeWidgets() {
|
||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
|
||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
|
||||
|
||||
// Create performance overlay
|
||||
performance_overlay = new PerformanceOverlay(this);
|
||||
performance_overlay->hide();
|
||||
|
||||
tas_label = new QLabel();
|
||||
tas_label->setObjectName(QStringLiteral("TASlabel"));
|
||||
tas_label->setFocusPolicy(Qt::NoFocus);
|
||||
@@ -1354,6 +1359,7 @@ void GMainWindow::InitializeHotkeys() {
|
||||
LinkActionShortcut(ui->action_Show_Filter_Bar, QStringLiteral("Toggle Filter Bar"));
|
||||
LinkActionShortcut(ui->action_Toggle_Grid_View, QStringLiteral("Toggle Grid View"));
|
||||
LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
|
||||
LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay"));
|
||||
LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen"));
|
||||
LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
|
||||
LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true);
|
||||
@@ -1454,6 +1460,10 @@ void GMainWindow::RestoreUIState() {
|
||||
|
||||
ui->action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar.GetValue());
|
||||
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
|
||||
ui->action_Show_Performance_Overlay->setChecked(UISettings::values.show_performance_overlay.GetValue());
|
||||
if (performance_overlay) {
|
||||
performance_overlay->SetVisible(ui->action_Show_Performance_Overlay->isChecked());
|
||||
}
|
||||
Debugger::ToggleConsole();
|
||||
}
|
||||
|
||||
@@ -1569,6 +1579,7 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars);
|
||||
connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar);
|
||||
connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar);
|
||||
connect_menu(ui->action_Show_Performance_Overlay, &GMainWindow::OnTogglePerformanceOverlay);
|
||||
connect_menu(ui->action_Toggle_Grid_View, &GMainWindow::OnToggleGridView);
|
||||
|
||||
connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720);
|
||||
@@ -4400,6 +4411,43 @@ void GMainWindow::OnToggleStatusBar() {
|
||||
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
|
||||
}
|
||||
|
||||
void GMainWindow::OnTogglePerformanceOverlay() {
|
||||
if (performance_overlay) {
|
||||
performance_overlay->SetVisible(ui->action_Show_Performance_Overlay->isChecked());
|
||||
}
|
||||
}
|
||||
|
||||
double GMainWindow::GetCurrentFPS() const {
|
||||
if (!system || !system->IsPoweredOn()) {
|
||||
return 0.0;
|
||||
}
|
||||
auto results = system->GetAndResetPerfStats();
|
||||
return results.average_game_fps;
|
||||
}
|
||||
|
||||
double GMainWindow::GetCurrentFrameTime() const {
|
||||
if (!system || !system->IsPoweredOn()) {
|
||||
return 0.0;
|
||||
}
|
||||
auto results = system->GetAndResetPerfStats();
|
||||
return results.frametime * 1000.0; // Convert to milliseconds
|
||||
}
|
||||
|
||||
u32 GMainWindow::GetShadersBuilding() const {
|
||||
if (!system || !system->IsPoweredOn()) {
|
||||
return 0;
|
||||
}
|
||||
return system->GPU().ShaderNotify().ShadersBuilding();
|
||||
}
|
||||
|
||||
double GMainWindow::GetEmulationSpeed() const {
|
||||
if (!system || !system->IsPoweredOn()) {
|
||||
return 0.0;
|
||||
}
|
||||
auto results = system->GetAndResetPerfStats();
|
||||
return results.emulation_speed * 100.0; // Convert to percentage
|
||||
}
|
||||
|
||||
void GMainWindow::OnAlbum() {
|
||||
constexpr u64 AlbumId = static_cast<u64>(Service::AM::AppletProgramId::PhotoViewer);
|
||||
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
|
||||
@@ -4765,6 +4813,7 @@ void GMainWindow::UpdateUISettings() {
|
||||
UISettings::values.display_titlebar = ui->action_Display_Dock_Widget_Headers->isChecked();
|
||||
UISettings::values.show_filter_bar = ui->action_Show_Filter_Bar->isChecked();
|
||||
UISettings::values.show_status_bar = ui->action_Show_Status_Bar->isChecked();
|
||||
UISettings::values.show_performance_overlay = ui->action_Show_Performance_Overlay->isChecked();
|
||||
UISettings::values.first_start = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,11 @@ class GRenderWindow;
|
||||
class LoadingScreen;
|
||||
class MicroProfileDialog;
|
||||
class OverlayDialog;
|
||||
class PerformanceOverlay;
|
||||
class ProfilerWidget;
|
||||
|
||||
// Forward declaration
|
||||
class PerformanceOverlay;
|
||||
class ControllerDialog;
|
||||
class QLabel;
|
||||
class MultiplayerState;
|
||||
@@ -159,6 +163,8 @@ class GMainWindow : public QMainWindow {
|
||||
/// Max number of recently loaded items to keep track of
|
||||
static const int max_recent_files_item = 10;
|
||||
|
||||
friend class PerformanceOverlay;
|
||||
|
||||
enum {
|
||||
CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES,
|
||||
CREATE_SHORTCUT_MSGBOX_SUCCESS,
|
||||
@@ -389,7 +395,14 @@ private slots:
|
||||
void OnToggleFilterBar();
|
||||
void OnToggleGridView();
|
||||
void OnToggleStatusBar();
|
||||
void OnTogglePerformanceOverlay();
|
||||
void OnDisplayTitleBars(bool);
|
||||
|
||||
// Performance overlay access methods
|
||||
double GetCurrentFPS() const;
|
||||
double GetCurrentFrameTime() const;
|
||||
u32 GetShadersBuilding() const;
|
||||
double GetEmulationSpeed() const;
|
||||
void InitializeHotkeys();
|
||||
void ToggleFullscreen();
|
||||
bool UsingExclusiveFullscreen();
|
||||
@@ -489,6 +502,7 @@ private:
|
||||
LoadingScreen* loading_screen;
|
||||
QTimer shutdown_timer;
|
||||
OverlayDialog* shutdown_dialog{};
|
||||
PerformanceOverlay* performance_overlay{};
|
||||
|
||||
GameListPlaceholder* game_list_placeholder;
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
<addaction name="action_Display_Dock_Widget_Headers"/>
|
||||
<addaction name="action_Show_Filter_Bar"/>
|
||||
<addaction name="action_Show_Status_Bar"/>
|
||||
<addaction name="action_Show_Performance_Overlay"/>
|
||||
<addaction name="action_Toggle_Grid_View"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_Reset_Window_Size"/>
|
||||
@@ -295,6 +296,17 @@
|
||||
<string>Show Status Bar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Show_Performance_Overlay">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show &Performance Overlay</string>
|
||||
</property>
|
||||
<property name="iconText">
|
||||
<string>Show Performance Overlay</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Toggle_Grid_View">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -101,6 +101,7 @@ struct Values {
|
||||
Setting<bool> display_titlebar{linkage, true, "displayTitleBars", Category::Ui};
|
||||
Setting<bool> show_filter_bar{linkage, true, "showFilterBar", Category::Ui};
|
||||
Setting<bool> show_status_bar{linkage, true, "showStatusBar", Category::Ui};
|
||||
Setting<bool> show_performance_overlay{linkage, false, "showPerformanceOverlay", Category::Ui};
|
||||
|
||||
SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
|
||||
ConfirmStop::Ask_Always,
|
||||
|
||||
369
src/citron/util/performance_overlay.cpp
Normal file
369
src/citron/util/performance_overlay.cpp
Normal file
@@ -0,0 +1,369 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QScreen>
|
||||
#include <QTimer>
|
||||
#include <QMouseEvent>
|
||||
#include <QtMath>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "citron/main.h"
|
||||
#include "citron/util/performance_overlay.h"
|
||||
#include "core/core.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
PerformanceOverlay::PerformanceOverlay(GMainWindow* parent)
|
||||
: QWidget(parent), main_window(parent) {
|
||||
|
||||
// Set up the widget properties
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
|
||||
// Initialize fonts with better typography
|
||||
title_font = QFont(QString::fromUtf8("Segoe UI"), 9, QFont::Medium);
|
||||
value_font = QFont(QString::fromUtf8("Segoe UI"), 11, QFont::Bold);
|
||||
small_font = QFont(QString::fromUtf8("Segoe UI"), 8, QFont::Normal);
|
||||
|
||||
// Initialize colors with a more modern palette
|
||||
background_color = QColor(20, 20, 20, 180); // Darker, more opaque background
|
||||
border_color = QColor(60, 60, 60, 120); // Subtle border
|
||||
text_color = QColor(220, 220, 220, 255); // Light gray text
|
||||
fps_color = QColor(76, 175, 80, 255); // Material Design green
|
||||
|
||||
// Graph colors
|
||||
graph_background_color = QColor(40, 40, 40, 100);
|
||||
graph_line_color = QColor(76, 175, 80, 200);
|
||||
graph_fill_color = QColor(76, 175, 80, 60);
|
||||
|
||||
// Set up timer for updates
|
||||
update_timer.setSingleShot(false);
|
||||
connect(&update_timer, &QTimer::timeout, this, &PerformanceOverlay::UpdatePerformanceStats);
|
||||
|
||||
// Set initial size - larger to accommodate the graph
|
||||
resize(220, 180);
|
||||
|
||||
// Position in top-left corner
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
PerformanceOverlay::~PerformanceOverlay() = default;
|
||||
|
||||
void PerformanceOverlay::SetVisible(bool visible) {
|
||||
if (is_visible == visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_visible = visible;
|
||||
|
||||
if (visible) {
|
||||
show();
|
||||
update_timer.start(500); // Update every 500ms for more accurate data
|
||||
} else {
|
||||
hide();
|
||||
update_timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void PerformanceOverlay::paintEvent(QPaintEvent* event) {
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::TextAntialiasing, true);
|
||||
|
||||
// Draw background with rounded corners and subtle shadow effect
|
||||
QPainterPath background_path;
|
||||
background_path.addRoundedRect(rect(), corner_radius, corner_radius);
|
||||
|
||||
// Draw subtle shadow
|
||||
QPainterPath shadow_path = background_path.translated(1, 1);
|
||||
painter.fillPath(shadow_path, QColor(0, 0, 0, 40));
|
||||
|
||||
// Draw main background
|
||||
painter.fillPath(background_path, background_color);
|
||||
|
||||
// Draw subtle border
|
||||
painter.setPen(QPen(border_color, border_width));
|
||||
painter.drawPath(background_path);
|
||||
|
||||
// Draw performance information
|
||||
DrawPerformanceInfo(painter);
|
||||
|
||||
// Draw frame graph
|
||||
DrawFrameGraph(painter);
|
||||
}
|
||||
|
||||
void PerformanceOverlay::resizeEvent(QResizeEvent* event) {
|
||||
QWidget::resizeEvent(event);
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
void PerformanceOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPos();
|
||||
widget_start_pos = this->pos();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void PerformanceOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||
if (is_dragging) {
|
||||
QPoint delta = event->globalPos() - drag_start_pos;
|
||||
move(widget_start_pos + delta);
|
||||
}
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void PerformanceOverlay::mouseReleaseEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
is_dragging = false;
|
||||
has_been_moved = true;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void PerformanceOverlay::UpdatePerformanceStats() {
|
||||
if (!main_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get shader building info (this is safe to call)
|
||||
shaders_building = main_window->GetShadersBuilding();
|
||||
|
||||
// Use a static counter to only call the performance methods occasionally
|
||||
// This reduces the chance of conflicts with the status bar updates
|
||||
static int update_counter = 0;
|
||||
update_counter++;
|
||||
|
||||
// Try to get performance data every 2nd update (every 1 second)
|
||||
if (update_counter % 2 == 0) {
|
||||
try {
|
||||
current_fps = main_window->GetCurrentFPS();
|
||||
current_frame_time = main_window->GetCurrentFrameTime();
|
||||
emulation_speed = main_window->GetEmulationSpeed();
|
||||
|
||||
// Validate the values
|
||||
if (std::isnan(current_fps) || current_fps < 0.0 || current_fps > 1000.0) {
|
||||
current_fps = 60.0;
|
||||
}
|
||||
if (std::isnan(current_frame_time) || current_frame_time < 0.0 || current_frame_time > 100.0) {
|
||||
current_frame_time = 16.67;
|
||||
}
|
||||
if (std::isnan(emulation_speed) || emulation_speed < 0.0 || emulation_speed > 1000.0) {
|
||||
emulation_speed = 100.0;
|
||||
}
|
||||
|
||||
// Ensure FPS and frame time are consistent
|
||||
if (current_fps > 0.0 && current_frame_time > 0.0) {
|
||||
// Recalculate frame time from FPS to ensure consistency
|
||||
current_frame_time = 1000.0 / current_fps;
|
||||
}
|
||||
} catch (...) {
|
||||
// If we get an exception, use the last known good values
|
||||
// Don't reset to defaults immediately
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have valid data yet, use defaults
|
||||
if (std::isnan(current_fps) || current_fps <= 0.0) {
|
||||
current_fps = 60.0;
|
||||
}
|
||||
if (std::isnan(current_frame_time) || current_frame_time <= 0.0) {
|
||||
current_frame_time = 16.67; // 60 FPS
|
||||
}
|
||||
if (std::isnan(emulation_speed) || emulation_speed <= 0.0) {
|
||||
emulation_speed = 100.0;
|
||||
}
|
||||
|
||||
// Add frame time to graph history (only if it's valid)
|
||||
if (current_frame_time > 0.0) {
|
||||
AddFrameTime(current_frame_time);
|
||||
}
|
||||
|
||||
// Update FPS color based on performance
|
||||
fps_color = GetFpsColor(current_fps);
|
||||
|
||||
// Trigger a repaint
|
||||
update();
|
||||
}
|
||||
|
||||
void PerformanceOverlay::UpdatePosition() {
|
||||
if (!main_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only position in top-left corner if we haven't been moved by the user
|
||||
if (!has_been_moved) {
|
||||
QPoint main_window_pos = main_window->mapToGlobal(QPoint(0, 0));
|
||||
move(main_window_pos.x() + 10, main_window_pos.y() + 10);
|
||||
}
|
||||
}
|
||||
|
||||
void PerformanceOverlay::DrawPerformanceInfo(QPainter& painter) {
|
||||
painter.setRenderHint(QPainter::TextAntialiasing, true);
|
||||
|
||||
int y_offset = padding + 12;
|
||||
const int line_height = 22;
|
||||
const int section_spacing = 4;
|
||||
|
||||
// Draw title with subtle styling
|
||||
painter.setFont(title_font);
|
||||
painter.setPen(text_color);
|
||||
painter.drawText(padding, y_offset, QString::fromUtf8("CITRON"));
|
||||
y_offset += line_height + section_spacing;
|
||||
|
||||
// Draw FPS with larger, more prominent display
|
||||
painter.setFont(value_font);
|
||||
painter.setPen(fps_color);
|
||||
QString fps_text = QString::fromUtf8("%1 FPS").arg(FormatFps(current_fps));
|
||||
painter.drawText(padding, y_offset, fps_text);
|
||||
y_offset += line_height;
|
||||
|
||||
// Draw frame time
|
||||
painter.setFont(small_font);
|
||||
painter.setPen(text_color);
|
||||
QString frame_time_text = QString::fromUtf8("Frame: %1 ms").arg(FormatFrameTime(current_frame_time));
|
||||
painter.drawText(padding, y_offset, frame_time_text);
|
||||
y_offset += line_height - 2;
|
||||
|
||||
// Draw emulation speed
|
||||
QString speed_text = QString::fromUtf8("Speed: %1%").arg(emulation_speed, 0, 'f', 0);
|
||||
painter.drawText(padding, y_offset, speed_text);
|
||||
y_offset += line_height - 2;
|
||||
|
||||
// Draw shader building info with accent color
|
||||
if (shaders_building > 0) {
|
||||
painter.setPen(QColor(255, 152, 0, 255)); // Material Design orange
|
||||
QString shader_text = QString::fromUtf8("Building: %1 shader(s)").arg(shaders_building);
|
||||
painter.drawText(padding, y_offset, shader_text);
|
||||
}
|
||||
}
|
||||
|
||||
void PerformanceOverlay::DrawFrameGraph(QPainter& painter) {
|
||||
if (frame_times.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int graph_y = height() - graph_height - padding;
|
||||
const int graph_width = width() - (padding * 2);
|
||||
const QRect graph_rect(padding, graph_y, graph_width, graph_height);
|
||||
|
||||
// Draw graph background
|
||||
painter.fillRect(graph_rect, graph_background_color);
|
||||
|
||||
// Calculate graph bounds
|
||||
const double min_val = std::max(0.0, min_frame_time - 1.0);
|
||||
const double max_val = std::max(16.67, max_frame_time + 1.0); // 16.67ms = 60 FPS
|
||||
const double range = max_val - min_val;
|
||||
|
||||
if (range <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw grid lines
|
||||
painter.setPen(QPen(QColor(80, 80, 80, 100), 1));
|
||||
const int grid_lines = 4;
|
||||
for (int i = 1; i < grid_lines; ++i) {
|
||||
const int y = graph_y + (graph_height * i) / grid_lines;
|
||||
painter.drawLine(graph_rect.left(), y, graph_rect.right(), y);
|
||||
}
|
||||
|
||||
// Draw 60 FPS line (16.67ms)
|
||||
const int fps60_y = graph_y + graph_height - static_cast<int>((16.67 - min_val) / range * graph_height);
|
||||
painter.setPen(QPen(QColor(255, 255, 255, 80), 1, Qt::DashLine));
|
||||
painter.drawLine(graph_rect.left(), fps60_y, graph_rect.right(), fps60_y);
|
||||
|
||||
// Draw frame time line
|
||||
painter.setPen(QPen(graph_line_color, 2));
|
||||
painter.setBrush(graph_fill_color);
|
||||
|
||||
QPainterPath graph_path;
|
||||
const int point_count = static_cast<int>(frame_times.size());
|
||||
const double x_step = static_cast<double>(graph_width) / (point_count - 1);
|
||||
|
||||
for (int i = 0; i < point_count; ++i) {
|
||||
const double frame_time = frame_times[i];
|
||||
const double normalized_y = (frame_time - min_val) / range;
|
||||
const int x = graph_rect.left() + static_cast<int>(i * x_step);
|
||||
const int y = graph_y + graph_height - static_cast<int>(normalized_y * graph_height);
|
||||
|
||||
if (i == 0) {
|
||||
graph_path.moveTo(x, y);
|
||||
} else {
|
||||
graph_path.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the path for filling
|
||||
graph_path.lineTo(graph_rect.right(), graph_rect.bottom());
|
||||
graph_path.lineTo(graph_rect.left(), graph_rect.bottom());
|
||||
graph_path.closeSubpath();
|
||||
|
||||
painter.drawPath(graph_path);
|
||||
|
||||
// Draw statistics text
|
||||
painter.setFont(small_font);
|
||||
painter.setPen(text_color);
|
||||
|
||||
const QString min_text = QString::fromUtf8("Min: %1ms").arg(FormatFrameTime(min_frame_time));
|
||||
const QString avg_text = QString::fromUtf8("Avg: %1ms").arg(FormatFrameTime(avg_frame_time));
|
||||
const QString max_text = QString::fromUtf8("Max: %1ms").arg(FormatFrameTime(max_frame_time));
|
||||
|
||||
painter.drawText(graph_rect.left(), graph_y - 5, min_text);
|
||||
painter.drawText(graph_rect.center().x() - painter.fontMetrics().horizontalAdvance(avg_text) / 2,
|
||||
graph_y - 5, avg_text);
|
||||
painter.drawText(graph_rect.right() - painter.fontMetrics().horizontalAdvance(max_text),
|
||||
graph_y - 5, max_text);
|
||||
}
|
||||
|
||||
void PerformanceOverlay::AddFrameTime(double frame_time_ms) {
|
||||
frame_times.push_back(frame_time_ms);
|
||||
|
||||
// Keep only the last MAX_FRAME_HISTORY frames
|
||||
if (frame_times.size() > MAX_FRAME_HISTORY) {
|
||||
frame_times.pop_front();
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
if (!frame_times.empty()) {
|
||||
min_frame_time = *std::min_element(frame_times.begin(), frame_times.end());
|
||||
max_frame_time = *std::max_element(frame_times.begin(), frame_times.end());
|
||||
avg_frame_time = std::accumulate(frame_times.begin(), frame_times.end(), 0.0) / frame_times.size();
|
||||
}
|
||||
}
|
||||
|
||||
QColor PerformanceOverlay::GetFpsColor(double fps) const {
|
||||
if (fps >= 55.0) {
|
||||
return QColor(76, 175, 80, 255); // Material Design green - Good performance
|
||||
} else if (fps >= 45.0) {
|
||||
return QColor(255, 152, 0, 255); // Material Design orange - Moderate performance
|
||||
} else if (fps >= 30.0) {
|
||||
return QColor(255, 87, 34, 255); // Material Design deep orange - Poor performance
|
||||
} else {
|
||||
return QColor(244, 67, 54, 255); // Material Design red - Very poor performance
|
||||
}
|
||||
}
|
||||
|
||||
QString PerformanceOverlay::FormatFps(double fps) const {
|
||||
if (std::isnan(fps) || fps < 0.0) {
|
||||
return QString::fromUtf8("0.0");
|
||||
}
|
||||
return QString::number(fps, 'f', 1);
|
||||
}
|
||||
|
||||
QString PerformanceOverlay::FormatFrameTime(double frame_time_ms) const {
|
||||
if (std::isnan(frame_time_ms) || frame_time_ms < 0.0) {
|
||||
return QString::fromUtf8("0.00");
|
||||
}
|
||||
return QString::number(frame_time_ms, 'f', 2);
|
||||
}
|
||||
90
src/citron/util/performance_overlay.h
Normal file
90
src/citron/util/performance_overlay.h
Normal file
@@ -0,0 +1,90 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QTimer>
|
||||
#include <QPainter>
|
||||
#include <QFont>
|
||||
#include <QColor>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
|
||||
class GMainWindow;
|
||||
|
||||
class PerformanceOverlay : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PerformanceOverlay(GMainWindow* parent);
|
||||
~PerformanceOverlay() override;
|
||||
|
||||
void SetVisible(bool visible);
|
||||
bool IsVisible() const { return is_visible; }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void UpdatePerformanceStats();
|
||||
|
||||
private:
|
||||
void UpdatePosition();
|
||||
void DrawPerformanceInfo(QPainter& painter);
|
||||
void DrawFrameGraph(QPainter& painter);
|
||||
QColor GetFpsColor(double fps) const;
|
||||
QString FormatFps(double fps) const;
|
||||
QString FormatFrameTime(double frame_time_ms) const;
|
||||
void AddFrameTime(double frame_time_ms);
|
||||
|
||||
GMainWindow* main_window;
|
||||
QTimer update_timer;
|
||||
|
||||
// Performance data
|
||||
double current_fps = 0.0;
|
||||
double current_frame_time = 0.0;
|
||||
int shaders_building = 0;
|
||||
double emulation_speed = 0.0;
|
||||
|
||||
// Frame graph data
|
||||
static constexpr size_t MAX_FRAME_HISTORY = 120; // 2 seconds at 60 FPS
|
||||
std::deque<double> frame_times;
|
||||
double min_frame_time = 0.0;
|
||||
double max_frame_time = 0.0;
|
||||
double avg_frame_time = 0.0;
|
||||
|
||||
// Display settings
|
||||
bool is_visible = false;
|
||||
QFont title_font;
|
||||
QFont value_font;
|
||||
QFont small_font;
|
||||
|
||||
// Colors
|
||||
QColor background_color;
|
||||
QColor border_color;
|
||||
QColor text_color;
|
||||
QColor fps_color;
|
||||
QColor graph_background_color;
|
||||
QColor graph_line_color;
|
||||
QColor graph_fill_color;
|
||||
|
||||
// Layout
|
||||
int padding = 12;
|
||||
int border_width = 1;
|
||||
int corner_radius = 10;
|
||||
int graph_height = 40;
|
||||
int graph_padding = 8;
|
||||
|
||||
// Drag functionality
|
||||
bool is_dragging = false;
|
||||
bool has_been_moved = false;
|
||||
QPoint drag_start_pos;
|
||||
QPoint widget_start_pos;
|
||||
};
|
||||
Reference in New Issue
Block a user