From 2d6669ade2d424d561bf9a250a2e0bb5dd8a98c8 Mon Sep 17 00:00:00 2001 From: Collecting Date: Mon, 5 Jan 2026 10:34:02 +0000 Subject: [PATCH] fix(gamescope): Fix Perf Overlay for Gamescope Signed-off-by: Collecting --- src/citron/util/performance_overlay.cpp | 493 ++++++++++++------------ 1 file changed, 240 insertions(+), 253 deletions(-) diff --git a/src/citron/util/performance_overlay.cpp b/src/citron/util/performance_overlay.cpp index 90f04038d..0503babc5 100644 --- a/src/citron/util/performance_overlay.cpp +++ b/src/citron/util/performance_overlay.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -22,7 +24,7 @@ #include #include #include -#pragma comment(lib, "wbemuuid.lib") // For MSVC, helps the linker find the library +#pragma comment(lib, "wbemuuid.lib") #endif #ifdef Q_OS_ANDROID @@ -37,85 +39,91 @@ #include "video_core/gpu.h" #include "video_core/renderer_base.h" -PerformanceOverlay::PerformanceOverlay(GMainWindow* parent) - : QWidget(parent), main_window(parent) { +PerformanceOverlay::PerformanceOverlay(QWidget* parent) : QWidget(UISettings::IsGamescope() ? nullptr : parent) { + if (parent) { + main_window = qobject_cast(parent); + } + + if (UISettings::IsGamescope()) { + setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus); + setAttribute(Qt::WA_ShowWithoutActivating); + } else { + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + } - // Set up the widget properties setAttribute(Qt::WA_TranslucentBackground, true); - setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_WState_ExplicitShowHide); - // 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); + if (UISettings::IsGamescope()) { + title_font = QFont(QString::fromUtf8("Segoe UI"), 9, QFont::Bold); + value_font = QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Bold); + small_font = QFont(QString::fromUtf8("Segoe UI"), 8, QFont::Normal); + setMinimumSize(160, 130); + resize(195, 160); + } else { + 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); + setMinimumSize(220, 180); + resize(220, 180); + } - temperature_color = QColor(76, 175, 80, 255); // Default to green + auto* layout = new QGridLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + size_grip = new QSizeGrip(this); + layout->addWidget(size_grip, 0, 0, Qt::AlignBottom | Qt::AlignRight); - // Graph colors + temperature_color = QColor(76, 175, 80, 255); 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); - // Connect to the main window's theme change signal - connect(parent, &GMainWindow::themeChanged, this, &PerformanceOverlay::UpdateTheme); - // Set the initial theme colors + if (main_window) { + connect(main_window, &GMainWindow::themeChanged, this, &PerformanceOverlay::UpdateTheme); + } + UpdateTheme(); - - // 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; + is_enabled = visible; + is_visible = visible; // Update the state so the check works next time if (visible) { show(); - update_timer.start(500); // Update every 500ms for more accurate data + update_timer.start(500); } else { + update_timer.stop(); // Stop the timer first 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)); + if (!UISettings::IsGamescope()) { + 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); } @@ -124,128 +132,110 @@ void PerformanceOverlay::resizeEvent(QResizeEvent* event) { UpdatePosition(); } +void PerformanceOverlay::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton && !size_grip->geometry().contains(event->pos())) { #if defined(Q_OS_LINUX) -// LINUX-SPECIFIC IMPLEMENTATION (Wayland Fix) -void PerformanceOverlay::mousePressEvent(QMouseEvent* event) { - if (event->button() == Qt::LeftButton) { - // Hand off window moving responsibility to the OS compositor. - if (windowHandle()) { + if (!UISettings::IsGamescope() && windowHandle()) { windowHandle()->startSystemMove(); + } else { + is_dragging = true; + drag_start_pos = event->globalPosition().toPoint() - this->pos(); } - } - QWidget::mousePressEvent(event); -} - -void PerformanceOverlay::mouseMoveEvent(QMouseEvent* event) { - // This function is intentionally left blank for dragging, as the - // system compositor now handles the entire move operation. - QWidget::mouseMoveEvent(event); -} - #else -// ORIGINAL IMPLEMENTATION -void PerformanceOverlay::mousePressEvent(QMouseEvent* event) { - if (event->button() == Qt::LeftButton) { is_dragging = true; - drag_start_pos = event->globalPosition().toPoint(); - widget_start_pos = this->pos(); - setCursor(Qt::ClosedHandCursor); + drag_start_pos = event->globalPosition().toPoint() - this->pos(); +#endif + event->accept(); } - QWidget::mousePressEvent(event); } void PerformanceOverlay::mouseMoveEvent(QMouseEvent* event) { if (is_dragging) { - QPoint delta = event->globalPosition().toPoint() - drag_start_pos; - move(widget_start_pos + delta); + move(event->globalPosition().toPoint() - drag_start_pos); + event->accept(); } - QWidget::mouseMoveEvent(event); } -#endif void PerformanceOverlay::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { is_dragging = false; has_been_moved = true; setCursor(Qt::ArrowCursor); + event->accept(); } QWidget::mouseReleaseEvent(event); } - void PerformanceOverlay::UpdatePerformanceStats() { - if (!main_window) { - return; + if (!main_window || !is_enabled) return; + + if (UISettings::IsGamescope()) { + bool ui_active = (QApplication::activePopupWidget() != nullptr); + + if (!ui_active) { + for (QWidget* w : QApplication::topLevelWidgets()) { + if (w->isVisible() && w != main_window && w != this && + !w->inherits("GRenderWindow") && + !w->inherits("VramOverlay") && + !w->inherits("ControllerOverlay") && + !w->inherits("PerformanceOverlay")) { + ui_active = true; + break; + } + } + } + + if (ui_active) { + if (!this->isHidden()) this->hide(); + return; + } + + if (this->isHidden()) { + this->show(); + } + } else { + // Desktop: Only force a show if the user actually has it enabled in the menu + if (is_enabled && this->isHidden()) { + this->show(); + } } - // 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; - } + 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 (current_fps > 0.0) current_frame_time = 1000.0 / current_fps; + } catch (...) {} } - // Update hardware temperatures every 4th update (every 2 seconds) if (update_counter % 4 == 0) { UpdateHardwareTemperatures(); } - // 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; - } + 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; + 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); - } + if (current_frame_time > 0.0) AddFrameTime(current_frame_time); - // Update FPS and Temperature colors based on performance fps_color = GetFpsColor(current_fps); temperature_color = GetTemperatureColor(std::max({cpu_temperature, gpu_temperature, battery_temperature})); - // Trigger a repaint update(); } void PerformanceOverlay::UpdateHardwareTemperatures() { - // Reset data cpu_temperature = 0.0f; gpu_temperature = 0.0f; cpu_sensor_type.clear(); @@ -254,41 +244,80 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { battery_temperature = 0.0f; #if defined(Q_OS_LINUX) - // --- Standard Linux Thermal Zone Reading --- - QDir thermal_dir(QString::fromUtf8("/sys/class/thermal/")); - QStringList filters{QString::fromUtf8("thermal_zone*")}; - QStringList thermal_zones = thermal_dir.entryList(filters, QDir::Dirs); + // 1. Read Battery Data (Steam Deck / Laptops) + QDir bat_dir(QStringLiteral("/sys/class/power_supply/")); + QStringList bats = bat_dir.entryList({QStringLiteral("BAT*")}, QDir::Dirs); + for (const QString& node : bats) { + QFile cap_file(bat_dir.filePath(node + QStringLiteral("/capacity"))); + if (cap_file.open(QIODevice::ReadOnly)) { + battery_percentage = cap_file.readAll().trimmed().toInt(); + cap_file.close(); - for (const QString& zone_name : thermal_zones) { - QFile type_file(thermal_dir.filePath(zone_name + QString::fromUtf8("/type"))); - if (!type_file.open(QIODevice::ReadOnly | QIODevice::Text)) continue; - QString type = QString::fromUtf8(type_file.readAll()).trimmed(); - type_file.close(); - - QFile temp_file(thermal_dir.filePath(zone_name + QString::fromUtf8("/temp"))); - if (!temp_file.open(QIODevice::ReadOnly | QIODevice::Text)) continue; - float temp = temp_file.readAll().trimmed().toFloat() / 1000.0f; - temp_file.close(); - - if (type.contains(QString::fromUtf8("x86_pkg_temp")) || type.contains(QString::fromUtf8("cpu"))) { - if (temp > cpu_temperature) { - cpu_temperature = temp; - cpu_sensor_type = QString::fromUtf8("CPU"); + QFile btemp_file(bat_dir.filePath(node + QStringLiteral("/temp"))); + if (btemp_file.open(QIODevice::ReadOnly)) { + float raw_temp = btemp_file.readAll().trimmed().toFloat(); + // Detect millidegrees (35000) or tenths (350) + battery_temperature = (raw_temp > 1000) ? raw_temp / 1000.0f : raw_temp / 10.0f; + btemp_file.close(); } - } else if (type.contains(QString::fromUtf8("radeon")) || type.contains(QString::fromUtf8("amdgpu")) || type.contains(QString::fromUtf8("nvidia")) || type.contains(QString::fromUtf8("nouveau"))) { - if (temp > gpu_temperature) { - gpu_temperature = temp; - gpu_sensor_type = QString::fromUtf8("GPU"); + break; + } + } + + // 2. Read APU/CPU Temperatures + QDir hwmon_dir(QStringLiteral("/sys/class/hwmon/")); + QStringList hwmons = hwmon_dir.entryList({QStringLiteral("hwmon*")}, QDir::Dirs); + for (const QString& h_node : hwmons) { + QFile name_file(hwmon_dir.filePath(h_node + QStringLiteral("/name"))); + if (!name_file.open(QIODevice::ReadOnly)) continue; + QString hw_name = QString::fromUtf8(name_file.readAll().trimmed()); + name_file.close(); + + // GPU Portion (Standard Steam Deck & Desktop AMD) + if (hw_name == QStringLiteral("amdgpu")) { + QFile t_file(hwmon_dir.filePath(h_node + QStringLiteral("/temp1_input"))); + if (t_file.open(QIODevice::ReadOnly)) { + gpu_temperature = t_file.readAll().trimmed().toFloat() / 1000.0f; + gpu_sensor_type = QStringLiteral("GPU"); + t_file.close(); + } + } + // CPU Portion (k10temp = AMD Deck/Desktop, coretemp = Intel Desktop) + else if (hw_name == QStringLiteral("k10temp") || hw_name == QStringLiteral("coretemp") || hw_name == QStringLiteral("zenpower")) { + // Check for temp1_input (AMD) or temp2_input (Intel coretemp usually starts at 2 for package) + QStringList input_candidates = {QStringLiteral("temp1_input"), QStringLiteral("temp2_input")}; + for (const auto& input : input_candidates) { + QFile t_file(hwmon_dir.filePath(h_node + QStringLiteral("/") + input)); + if (t_file.open(QIODevice::ReadOnly)) { + cpu_temperature = t_file.readAll().trimmed().toFloat() / 1000.0f; + cpu_sensor_type = QStringLiteral("CPU"); + t_file.close(); + if (cpu_temperature > 0) break; + } + } + } + } + + // 3. Fallback to generic thermal_zones + if (cpu_temperature <= 0.0f) { + QDir thermal_dir(QStringLiteral("/sys/class/thermal/")); + QStringList thermal_zones = thermal_dir.entryList({QStringLiteral("thermal_zone*")}, QDir::Dirs); + for (const QString& zone_name : thermal_zones) { + QFile temp_file(thermal_dir.filePath(zone_name + QStringLiteral("/temp"))); + if (temp_file.open(QIODevice::ReadOnly)) { + cpu_temperature = temp_file.readAll().trimmed().toFloat() / 1000.0f; + cpu_sensor_type = QStringLiteral("CPU"); + temp_file.close(); + if (cpu_temperature > 0) break; } } } #endif +} #if defined(Q_OS_ANDROID) - // This uses QtAndroid Extras to get battery info from the Android system. - // NOTE: This requires the QtAndroidExtras module to be linked in the build. QJniObject battery_status = QJniObject::callStaticObjectMethod( - "android/content/CONTEXT", "registerReceiver", + "android/content/Context", "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;", nullptr, new QJniObject("android.content.IntentFilter", "(Ljava/lang/String;)V", "android.intent.action.BATTERY_CHANGED")); @@ -300,12 +329,8 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { int temp_tenths = battery_status.callMethod("getIntExtra", "(Ljava/lang/String;I)I", QJniObject::fromString("temperature").object(), -1); - if (scale > 0) { - battery_percentage = (level * 100) / scale; - } - if (temp_tenths > 0) { - battery_temperature = static_cast(temp_tenths) / 10.0f; - } + if (scale > 0) battery_percentage = (level * 100) / scale; + if (temp_tenths > 0) battery_temperature = static_cast(temp_tenths) / 10.0f; } #endif @@ -313,9 +338,7 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { HRESULT hres; IWbemLocator* pLoc = nullptr; IWbemServices* pSvc = nullptr; - hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc); - if (SUCCEEDED(hres)) { hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\WMI"), NULL, NULL, 0, NULL, 0, 0, &pSvc); if (SUCCEEDED(hres)) { @@ -331,12 +354,11 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { while (pEnumerator) { pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if (uReturn == 0) break; - VARIANT vtProp; pclsObj->Get(L"CurrentTemperature", 0, &vtProp, 0, 0); float temp_kelvin = vtProp.uintVal / 10.0f; cpu_temperature = temp_kelvin - 273.15f; - cpu_sensor_type = QString::fromUtf8("CPU"); + cpu_sensor_type = QStringLiteral("CPU"); VariantClear(&vtProp); pclsObj->Release(); } @@ -348,15 +370,9 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { if(pSvc) pSvc->Release(); if(pLoc) pLoc->Release(); #endif -} 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) { + if (main_window && !has_been_moved) { QPoint main_window_pos = main_window->mapToGlobal(QPoint(0, 0)); move(main_window_pos.x() + 10, main_window_pos.y() + 10); } @@ -365,92 +381,85 @@ void PerformanceOverlay::UpdatePosition() { void PerformanceOverlay::DrawPerformanceInfo(QPainter& painter) { painter.setRenderHint(QPainter::TextAntialiasing, true); - int y_offset = padding; - const int line_height = 20; + // Dynamic spacing based on font size to prevent squishing + const int title_step = painter.fontMetrics().height() + 2; + const int stat_step = painter.fontMetrics().height() + 2; - // Draw title + int y_left = (padding / 2) + painter.fontMetrics().ascent(); + int y_right = y_left + 10; + + // 1. Draw Title (Left) painter.setFont(title_font); painter.setPen(text_color); - painter.drawText(padding, y_offset + 12, QString::fromUtf8("CITRON")); + painter.drawText(padding, y_left, QStringLiteral("CITRON PERFORMANCE")); - int y_offset_right = padding; - const int line_height_right = 18; - - // Draw Temperatures + // 2. Draw Hardware Stats (Right Column) painter.setFont(small_font); + const int hw_step = UISettings::IsGamescope() ? 16 : 20; - float core_temp_to_display = std::max(cpu_temperature, gpu_temperature); - if (core_temp_to_display > 0.0f) { - QString core_label = gpu_temperature > cpu_temperature ? gpu_sensor_type : cpu_sensor_type; - QString core_temp_text = QString::fromUtf8("%1: %2°C").arg(core_label).arg(core_temp_to_display, 0, 'f', 0); - painter.setPen(GetTemperatureColor(core_temp_to_display)); - int text_width = painter.fontMetrics().horizontalAdvance(core_temp_text); - painter.drawText(width() - padding - text_width, y_offset_right + 12, core_temp_text); + if (cpu_temperature > 0.0f) { + QString cpu_text = QStringLiteral("CPU:%1°C").arg(cpu_temperature, 0, 'f', 0); + painter.setPen(GetTemperatureColor(cpu_temperature)); + int tw = painter.fontMetrics().horizontalAdvance(cpu_text); + painter.drawText(width() - padding - tw, y_right, cpu_text); + y_right += hw_step; + } + + if (gpu_temperature > 0.0f) { + QString gpu_text = QStringLiteral("GPU:%1°C").arg(gpu_temperature, 0, 'f', 0); + painter.setPen(GetTemperatureColor(gpu_temperature)); + int tw = painter.fontMetrics().horizontalAdvance(gpu_text); + painter.drawText(width() - padding - tw, y_right, gpu_text); + y_right += hw_step; } - y_offset_right += line_height_right; - // Draw Battery info if (battery_percentage > 0) { - QString batt_text = QString::fromUtf8("Batt: %1%").arg(battery_percentage); + QString batt_text = QStringLiteral("Battery %:%1%").arg(battery_percentage); if (battery_temperature > 0.0f) { - batt_text += QString::fromUtf8(" (%1°C)").arg(battery_temperature, 0, 'f', 0); + batt_text += QStringLiteral(" (%1°C)").arg(battery_temperature, 0, 'f', 0); } painter.setPen(text_color); - int text_width = painter.fontMetrics().horizontalAdvance(batt_text); - painter.drawText(width() - padding - text_width, y_offset_right + 12, batt_text); + int tw = painter.fontMetrics().horizontalAdvance(batt_text); + painter.drawText(width() - padding - tw, y_right, batt_text); } - y_offset += line_height + 4; - - // Draw FPS + // 3. Draw FPS (Left Column) + y_left += title_step; 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; + painter.drawText(padding, y_left, QStringLiteral("%1 FPS").arg(FormatFps(current_fps))); - // Draw frame time + // 4. Draw Small Stats (Left Column) + y_left += title_step; 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; + painter.drawText(padding, y_left, QStringLiteral("Frame:%1 ms").arg(FormatFrameTime(current_frame_time))); - // 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; + y_left += stat_step; + painter.drawText(padding, y_left, QStringLiteral("Speed:%1%").arg(emulation_speed, 0, 'f', 0)); - // 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); + y_left += stat_step; + painter.setPen(QColor(255, 152, 0)); + painter.drawText(padding, y_left, QStringLiteral("Building:%1").arg(shaders_building)); } } void PerformanceOverlay::DrawFrameGraph(QPainter& painter) { - if (frame_times.empty()) { - return; - } + 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 max_val = std::max(16.67, max_frame_time + 1.0); const double range = max_val - min_val; + if (range <= 0.0) return; - if (range <= 0.0) { - return; - } - - // Draw grid lines + // Grid lines painter.setPen(QPen(QColor(80, 80, 80, 100), 1)); const int grid_lines = 4; for (int i = 1; i < grid_lines; ++i) { @@ -458,12 +467,11 @@ void PerformanceOverlay::DrawFrameGraph(QPainter& painter) { painter.drawLine(graph_rect.left(), y, graph_rect.right(), y); } - // Draw 60 FPS line (16.67ms) + // 60 FPS Target line const int fps60_y = graph_y + graph_height - static_cast((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); @@ -476,45 +484,39 @@ void PerformanceOverlay::DrawFrameGraph(QPainter& painter) { const double normalized_y = (frame_time - min_val) / range; const int x = graph_rect.left() + static_cast(i * x_step); const int y = graph_y + graph_height - static_cast(normalized_y * graph_height); - - if (i == 0) { - graph_path.moveTo(x, y); - } else { - graph_path.lineTo(x, y); - } + 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)); + const QString min_str = QStringLiteral("Min:%1ms").arg(FormatFrameTime(min_frame_time)); + const QString avg_str = QStringLiteral("Avg:%2ms").arg(FormatFrameTime(avg_frame_time)); + const QString max_str = QStringLiteral("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); + // Combine into one line for measurement + const QString full_line = QStringLiteral("%1 %2 %3").arg(min_str, avg_str, max_str); + int total_width = painter.fontMetrics().horizontalAdvance(full_line); + + // If there is enough room, flatten it across the top. Otherwise, stack it. + if (total_width < graph_width - 10) { + // Flat layout + painter.drawText(graph_rect.left(), graph_y - 6, full_line); + } else { + // Stacked layout (Fallback for small windows/High-DPI scaling) + painter.drawText(graph_rect.left(), graph_y - 18, QStringLiteral("%1 %2").arg(min_str, avg_str)); + painter.drawText(graph_rect.left(), graph_y - 4, max_str); + } } 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.size() > MAX_FRAME_HISTORY) frame_times.pop_front(); 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()); @@ -523,54 +525,39 @@ void PerformanceOverlay::AddFrameTime(double frame_time_ms) { } 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 - } + if (fps >= 55.0) return QColor(76, 175, 80, 255); + if (fps >= 45.0) return QColor(255, 152, 0, 255); + if (fps >= 30.0) return QColor(255, 87, 34, 255); + return QColor(244, 67, 54, 255); } QColor PerformanceOverlay::GetTemperatureColor(float temperature) const { - if (temperature > 70.0f) { - return QColor(244, 67, 54, 255); // Material Design red - } else if (temperature > 60.0f) { - return QColor(255, 152, 0, 255); // Material Design orange - } else { - return QColor(76, 175, 80, 255); // Material Design green - } + if (temperature > 85.0f) return QColor(244, 67, 54, 255); + if (temperature > 75.0f) return QColor(255, 152, 0, 255); + return QColor(76, 175, 80, 255); } QString PerformanceOverlay::FormatFps(double fps) const { - if (std::isnan(fps) || fps < 0.0) { - return QString::fromUtf8("0.0"); - } + 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"); - } + if (std::isnan(frame_time_ms) || frame_time_ms < 0.0) return QString::fromUtf8("0.00"); return QString::number(frame_time_ms, 'f', 2); } void PerformanceOverlay::UpdateTheme() { if (UISettings::IsDarkTheme()) { - // Dark Theme Colors (your original values) - background_color = QColor(20, 20, 20, 200); // Slightly more opaque + background_color = QColor(20, 20, 20, 200); border_color = QColor(60, 60, 60, 120); text_color = QColor(220, 220, 220, 255); graph_background_color = QColor(40, 40, 40, 100); } else { - // Light Theme Colors background_color = QColor(245, 245, 245, 220); border_color = QColor(200, 200, 200, 120); text_color = QColor(20, 20, 20, 255); graph_background_color = QColor(220, 220, 220, 100); } - update(); // Force a repaint with the new colors + update(); }