mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-20 19:13:56 +00:00
feat: add VRAM monitoring overlay with modern UI
- Clean dark-themed overlay with real-time VRAM usage display - Memory breakdown (Buffers, Textures, Staging) with color coding - Interactive graph with 2-minute history and leak detection - VRAM mode indicator with special highlighting for Insane mode - Draggable interface with persistent positioning - Menu integration with keyboard shortcut support - Safe Vulkan renderer integration with exception handling Files: CMakeLists.txt, main.cpp, main.h, main.ui, uisettings.h, vram_overlay.cpp, vram_overlay.h Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
@@ -226,6 +226,8 @@ add_executable(citron
|
|||||||
util/overlay_dialog.ui
|
util/overlay_dialog.ui
|
||||||
util/performance_overlay.cpp
|
util/performance_overlay.cpp
|
||||||
util/performance_overlay.h
|
util/performance_overlay.h
|
||||||
|
util/vram_overlay.cpp
|
||||||
|
util/vram_overlay.h
|
||||||
util/sequence_dialog/sequence_dialog.cpp
|
util/sequence_dialog/sequence_dialog.cpp
|
||||||
util/sequence_dialog/sequence_dialog.h
|
util/sequence_dialog/sequence_dialog.h
|
||||||
util/url_request_interceptor.cpp
|
util/url_request_interceptor.cpp
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
|||||||
#include "util/overlay_dialog.h"
|
#include "util/overlay_dialog.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
#include "video_core/shader_notify.h"
|
#include "video_core/shader_notify.h"
|
||||||
#include "citron/about_dialog.h"
|
#include "citron/about_dialog.h"
|
||||||
#include "citron/bootmanager.h"
|
#include "citron/bootmanager.h"
|
||||||
@@ -166,6 +168,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
|||||||
#include "citron/updater/updater_service.h"
|
#include "citron/updater/updater_service.h"
|
||||||
#include "citron/util/clickable_label.h"
|
#include "citron/util/clickable_label.h"
|
||||||
#include "citron/util/performance_overlay.h"
|
#include "citron/util/performance_overlay.h"
|
||||||
|
#include "citron/util/vram_overlay.h"
|
||||||
#include "citron/vk_device_info.h"
|
#include "citron/vk_device_info.h"
|
||||||
|
|
||||||
#ifdef CITRON_CRASH_DUMPS
|
#ifdef CITRON_CRASH_DUMPS
|
||||||
@@ -1080,6 +1083,9 @@ void GMainWindow::InitializeWidgets() {
|
|||||||
performance_overlay = new PerformanceOverlay(this);
|
performance_overlay = new PerformanceOverlay(this);
|
||||||
performance_overlay->hide();
|
performance_overlay->hide();
|
||||||
|
|
||||||
|
vram_overlay = new VramOverlay(this);
|
||||||
|
vram_overlay->hide();
|
||||||
|
|
||||||
tas_label = new QLabel();
|
tas_label = new QLabel();
|
||||||
tas_label->setObjectName(QStringLiteral("TASlabel"));
|
tas_label->setObjectName(QStringLiteral("TASlabel"));
|
||||||
tas_label->setFocusPolicy(Qt::NoFocus);
|
tas_label->setFocusPolicy(Qt::NoFocus);
|
||||||
@@ -1360,6 +1366,7 @@ void GMainWindow::InitializeHotkeys() {
|
|||||||
LinkActionShortcut(ui->action_Toggle_Grid_View, QStringLiteral("Toggle Grid View"));
|
LinkActionShortcut(ui->action_Toggle_Grid_View, QStringLiteral("Toggle Grid View"));
|
||||||
LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
|
LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
|
||||||
LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay"));
|
LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay"));
|
||||||
|
LinkActionShortcut(ui->action_Show_Vram_Overlay, QStringLiteral("Toggle VRAM Overlay"));
|
||||||
LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen"));
|
LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen"));
|
||||||
LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
|
LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
|
||||||
LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true);
|
LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true);
|
||||||
@@ -1464,6 +1471,11 @@ void GMainWindow::RestoreUIState() {
|
|||||||
if (performance_overlay) {
|
if (performance_overlay) {
|
||||||
performance_overlay->SetVisible(ui->action_Show_Performance_Overlay->isChecked());
|
performance_overlay->SetVisible(ui->action_Show_Performance_Overlay->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui->action_Show_Vram_Overlay->setChecked(UISettings::values.show_vram_overlay.GetValue());
|
||||||
|
if (vram_overlay) {
|
||||||
|
vram_overlay->SetVisible(ui->action_Show_Vram_Overlay->isChecked());
|
||||||
|
}
|
||||||
Debugger::ToggleConsole();
|
Debugger::ToggleConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1580,6 +1592,7 @@ void GMainWindow::ConnectMenuEvents() {
|
|||||||
connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar);
|
connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar);
|
||||||
connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar);
|
connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar);
|
||||||
connect_menu(ui->action_Show_Performance_Overlay, &GMainWindow::OnTogglePerformanceOverlay);
|
connect_menu(ui->action_Show_Performance_Overlay, &GMainWindow::OnTogglePerformanceOverlay);
|
||||||
|
connect_menu(ui->action_Show_Vram_Overlay, &GMainWindow::OnToggleVramOverlay);
|
||||||
connect_menu(ui->action_Toggle_Grid_View, &GMainWindow::OnToggleGridView);
|
connect_menu(ui->action_Toggle_Grid_View, &GMainWindow::OnToggleGridView);
|
||||||
|
|
||||||
connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720);
|
connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720);
|
||||||
@@ -4417,6 +4430,12 @@ void GMainWindow::OnTogglePerformanceOverlay() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnToggleVramOverlay() {
|
||||||
|
if (vram_overlay) {
|
||||||
|
vram_overlay->SetVisible(ui->action_Show_Vram_Overlay->isChecked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double GMainWindow::GetCurrentFPS() const {
|
double GMainWindow::GetCurrentFPS() const {
|
||||||
if (!system || !system->IsPoweredOn()) {
|
if (!system || !system->IsPoweredOn()) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
@@ -4440,6 +4459,112 @@ u32 GMainWindow::GetShadersBuilding() const {
|
|||||||
return system->GPU().ShaderNotify().ShadersBuilding();
|
return system->GPU().ShaderNotify().ShadersBuilding();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 GMainWindow::GetTotalVram() const {
|
||||||
|
if (!system || !system->IsPoweredOn()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto& gpu = system->GPU();
|
||||||
|
VideoCore::RendererBase& renderer = gpu.Renderer();
|
||||||
|
// Check if it's a Vulkan renderer
|
||||||
|
Vulkan::RendererVulkan* vulkan_renderer = dynamic_cast<Vulkan::RendererVulkan*>(&renderer);
|
||||||
|
if (vulkan_renderer) {
|
||||||
|
VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer();
|
||||||
|
Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast<Vulkan::RasterizerVulkan*>(rasterizer);
|
||||||
|
if (vulkan_rasterizer) {
|
||||||
|
return vulkan_rasterizer->GetTotalVram();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Ignore exceptions
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GMainWindow::GetUsedVram() const {
|
||||||
|
if (!system || !system->IsPoweredOn()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto& gpu = system->GPU();
|
||||||
|
VideoCore::RendererBase& renderer = gpu.Renderer();
|
||||||
|
Vulkan::RendererVulkan* vulkan_renderer = dynamic_cast<Vulkan::RendererVulkan*>(&renderer);
|
||||||
|
if (vulkan_renderer) {
|
||||||
|
VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer();
|
||||||
|
Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast<Vulkan::RasterizerVulkan*>(rasterizer);
|
||||||
|
if (vulkan_rasterizer) {
|
||||||
|
return vulkan_rasterizer->GetUsedVram();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Ignore exceptions
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GMainWindow::GetBufferMemoryUsage() const {
|
||||||
|
if (!system || !system->IsPoweredOn()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto& gpu = system->GPU();
|
||||||
|
VideoCore::RendererBase& renderer = gpu.Renderer();
|
||||||
|
Vulkan::RendererVulkan* vulkan_renderer = dynamic_cast<Vulkan::RendererVulkan*>(&renderer);
|
||||||
|
if (vulkan_renderer) {
|
||||||
|
VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer();
|
||||||
|
Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast<Vulkan::RasterizerVulkan*>(rasterizer);
|
||||||
|
if (vulkan_rasterizer) {
|
||||||
|
return vulkan_rasterizer->GetBufferMemoryUsage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Ignore exceptions
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GMainWindow::GetTextureMemoryUsage() const {
|
||||||
|
if (!system || !system->IsPoweredOn()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto& gpu = system->GPU();
|
||||||
|
VideoCore::RendererBase& renderer = gpu.Renderer();
|
||||||
|
Vulkan::RendererVulkan* vulkan_renderer = dynamic_cast<Vulkan::RendererVulkan*>(&renderer);
|
||||||
|
if (vulkan_renderer) {
|
||||||
|
VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer();
|
||||||
|
Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast<Vulkan::RasterizerVulkan*>(rasterizer);
|
||||||
|
if (vulkan_rasterizer) {
|
||||||
|
return vulkan_rasterizer->GetTextureMemoryUsage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Ignore exceptions
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GMainWindow::GetStagingMemoryUsage() const {
|
||||||
|
if (!system || !system->IsPoweredOn()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto& gpu = system->GPU();
|
||||||
|
VideoCore::RendererBase& renderer = gpu.Renderer();
|
||||||
|
Vulkan::RendererVulkan* vulkan_renderer = dynamic_cast<Vulkan::RendererVulkan*>(&renderer);
|
||||||
|
if (vulkan_renderer) {
|
||||||
|
VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer();
|
||||||
|
Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast<Vulkan::RasterizerVulkan*>(rasterizer);
|
||||||
|
if (vulkan_rasterizer) {
|
||||||
|
return vulkan_rasterizer->GetStagingMemoryUsage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Ignore exceptions
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
double GMainWindow::GetEmulationSpeed() const {
|
double GMainWindow::GetEmulationSpeed() const {
|
||||||
if (!system || !system->IsPoweredOn()) {
|
if (!system || !system->IsPoweredOn()) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
@@ -4814,6 +4939,7 @@ void GMainWindow::UpdateUISettings() {
|
|||||||
UISettings::values.show_filter_bar = ui->action_Show_Filter_Bar->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_status_bar = ui->action_Show_Status_Bar->isChecked();
|
||||||
UISettings::values.show_performance_overlay = ui->action_Show_Performance_Overlay->isChecked();
|
UISettings::values.show_performance_overlay = ui->action_Show_Performance_Overlay->isChecked();
|
||||||
|
UISettings::values.show_vram_overlay = ui->action_Show_Vram_Overlay->isChecked();
|
||||||
UISettings::values.first_start = false;
|
UISettings::values.first_start = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class LoadingScreen;
|
|||||||
class MicroProfileDialog;
|
class MicroProfileDialog;
|
||||||
class OverlayDialog;
|
class OverlayDialog;
|
||||||
class PerformanceOverlay;
|
class PerformanceOverlay;
|
||||||
|
class VramOverlay;
|
||||||
class ProfilerWidget;
|
class ProfilerWidget;
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
@@ -164,6 +165,7 @@ class GMainWindow : public QMainWindow {
|
|||||||
static const int max_recent_files_item = 10;
|
static const int max_recent_files_item = 10;
|
||||||
|
|
||||||
friend class PerformanceOverlay;
|
friend class PerformanceOverlay;
|
||||||
|
friend class VramOverlay;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES,
|
CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES,
|
||||||
@@ -396,6 +398,7 @@ private slots:
|
|||||||
void OnToggleGridView();
|
void OnToggleGridView();
|
||||||
void OnToggleStatusBar();
|
void OnToggleStatusBar();
|
||||||
void OnTogglePerformanceOverlay();
|
void OnTogglePerformanceOverlay();
|
||||||
|
void OnToggleVramOverlay();
|
||||||
void OnDisplayTitleBars(bool);
|
void OnDisplayTitleBars(bool);
|
||||||
|
|
||||||
// Performance overlay access methods
|
// Performance overlay access methods
|
||||||
@@ -403,6 +406,13 @@ private slots:
|
|||||||
double GetCurrentFrameTime() const;
|
double GetCurrentFrameTime() const;
|
||||||
u32 GetShadersBuilding() const;
|
u32 GetShadersBuilding() const;
|
||||||
double GetEmulationSpeed() const;
|
double GetEmulationSpeed() const;
|
||||||
|
|
||||||
|
// VRAM overlay access methods
|
||||||
|
u64 GetTotalVram() const;
|
||||||
|
u64 GetUsedVram() const;
|
||||||
|
u64 GetBufferMemoryUsage() const;
|
||||||
|
u64 GetTextureMemoryUsage() const;
|
||||||
|
u64 GetStagingMemoryUsage() const;
|
||||||
void InitializeHotkeys();
|
void InitializeHotkeys();
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
bool UsingExclusiveFullscreen();
|
bool UsingExclusiveFullscreen();
|
||||||
@@ -503,6 +513,7 @@ private:
|
|||||||
QTimer shutdown_timer;
|
QTimer shutdown_timer;
|
||||||
OverlayDialog* shutdown_dialog{};
|
OverlayDialog* shutdown_dialog{};
|
||||||
PerformanceOverlay* performance_overlay{};
|
PerformanceOverlay* performance_overlay{};
|
||||||
|
VramOverlay* vram_overlay{};
|
||||||
|
|
||||||
GameListPlaceholder* game_list_placeholder;
|
GameListPlaceholder* game_list_placeholder;
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,7 @@
|
|||||||
<addaction name="action_Show_Filter_Bar"/>
|
<addaction name="action_Show_Filter_Bar"/>
|
||||||
<addaction name="action_Show_Status_Bar"/>
|
<addaction name="action_Show_Status_Bar"/>
|
||||||
<addaction name="action_Show_Performance_Overlay"/>
|
<addaction name="action_Show_Performance_Overlay"/>
|
||||||
|
<addaction name="action_Show_Vram_Overlay"/>
|
||||||
<addaction name="action_Toggle_Grid_View"/>
|
<addaction name="action_Toggle_Grid_View"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="menu_Reset_Window_Size"/>
|
<addaction name="menu_Reset_Window_Size"/>
|
||||||
@@ -307,6 +308,17 @@
|
|||||||
<string>Show Performance Overlay</string>
|
<string>Show Performance Overlay</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_Show_Vram_Overlay">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Show &VRAM Monitor</string>
|
||||||
|
</property>
|
||||||
|
<property name="iconText">
|
||||||
|
<string>Show VRAM Monitor</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="action_Toggle_Grid_View">
|
<action name="action_Toggle_Grid_View">
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ struct Values {
|
|||||||
Setting<bool> show_filter_bar{linkage, true, "showFilterBar", 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_status_bar{linkage, true, "showStatusBar", Category::Ui};
|
||||||
Setting<bool> show_performance_overlay{linkage, false, "showPerformanceOverlay", Category::Ui};
|
Setting<bool> show_performance_overlay{linkage, false, "showPerformanceOverlay", Category::Ui};
|
||||||
|
Setting<bool> show_vram_overlay{linkage, false, "showVramOverlay", Category::Ui};
|
||||||
|
|
||||||
SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
|
SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
|
||||||
ConfirmStop::Ask_Always,
|
ConfirmStop::Ask_Always,
|
||||||
|
|||||||
375
src/citron/util/vram_overlay.cpp
Normal file
375
src/citron/util/vram_overlay.cpp
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
// 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/vram_overlay.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "video_core/gpu.h"
|
||||||
|
#include "video_core/renderer_base.h"
|
||||||
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
|
||||||
|
VramOverlay::VramOverlay(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 clean typography
|
||||||
|
title_font = QFont(QString::fromUtf8("Segoe UI"), 11, QFont::Bold);
|
||||||
|
value_font = QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Medium);
|
||||||
|
small_font = QFont(QString::fromUtf8("Segoe UI"), 9, QFont::Normal);
|
||||||
|
warning_font = QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Bold);
|
||||||
|
|
||||||
|
// Modern dark theme colors
|
||||||
|
background_color = QColor(15, 15, 15, 220); // Dark background with good opacity
|
||||||
|
border_color = QColor(45, 45, 45, 255); // Subtle border
|
||||||
|
text_color = QColor(240, 240, 240, 255); // Clean white text
|
||||||
|
secondary_text_color = QColor(180, 180, 180, 255); // Secondary text
|
||||||
|
|
||||||
|
// VRAM usage colors - modern palette
|
||||||
|
vram_safe_color = QColor(76, 175, 80, 255); // Green
|
||||||
|
vram_warning_color = QColor(255, 193, 7, 255); // Amber
|
||||||
|
vram_danger_color = QColor(244, 67, 54, 255); // Red
|
||||||
|
leak_warning_color = QColor(255, 152, 0, 255); // Orange
|
||||||
|
|
||||||
|
// Graph colors - clean and modern
|
||||||
|
graph_background_color = QColor(25, 25, 25, 255);
|
||||||
|
graph_grid_color = QColor(60, 60, 60, 100);
|
||||||
|
graph_line_color = QColor(76, 175, 80, 255);
|
||||||
|
graph_fill_color = QColor(76, 175, 80, 40);
|
||||||
|
|
||||||
|
// Set up timer for updates
|
||||||
|
update_timer.setSingleShot(false);
|
||||||
|
connect(&update_timer, &QTimer::timeout, this, &VramOverlay::UpdateVramStats);
|
||||||
|
|
||||||
|
// Set clean, compact size
|
||||||
|
resize(280, 200);
|
||||||
|
|
||||||
|
// Position in top-right corner
|
||||||
|
UpdatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
VramOverlay::~VramOverlay() = default;
|
||||||
|
|
||||||
|
void VramOverlay::SetVisible(bool visible) {
|
||||||
|
if (is_visible == visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_visible = visible;
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
show();
|
||||||
|
update_timer.start(1000); // Update every 1 second
|
||||||
|
} else {
|
||||||
|
hide();
|
||||||
|
update_timer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::UpdatePosition() {
|
||||||
|
if (main_window) {
|
||||||
|
QPoint main_window_pos = main_window->pos();
|
||||||
|
QSize main_window_size = main_window->size();
|
||||||
|
|
||||||
|
// Position in top-right corner with proper margin
|
||||||
|
move(main_window_pos.x() + main_window_size.width() - width() - 15, main_window_pos.y() + 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::paintEvent(QPaintEvent* event) {
|
||||||
|
Q_UNUSED(event)
|
||||||
|
|
||||||
|
QPainter painter(this);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
painter.setRenderHint(QPainter::TextAntialiasing, true);
|
||||||
|
|
||||||
|
// Draw rounded background
|
||||||
|
QPainterPath background_path;
|
||||||
|
background_path.addRoundedRect(rect(), corner_radius, corner_radius);
|
||||||
|
painter.fillPath(background_path, background_color);
|
||||||
|
|
||||||
|
// Draw subtle border
|
||||||
|
painter.setPen(QPen(border_color, border_width));
|
||||||
|
painter.drawPath(background_path);
|
||||||
|
|
||||||
|
// Draw content sections
|
||||||
|
DrawVramInfo(painter);
|
||||||
|
DrawVramGraph(painter);
|
||||||
|
|
||||||
|
if (current_vram_data.leak_detected) {
|
||||||
|
DrawLeakWarning(painter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::DrawVramInfo(QPainter& painter) {
|
||||||
|
const int section_padding = 16;
|
||||||
|
const int line_height = 16;
|
||||||
|
const int section_spacing = 8;
|
||||||
|
|
||||||
|
int y_offset = section_padding;
|
||||||
|
|
||||||
|
// Title
|
||||||
|
painter.setFont(title_font);
|
||||||
|
painter.setPen(text_color);
|
||||||
|
painter.drawText(section_padding, y_offset, QString::fromUtf8("VRAM Monitor"));
|
||||||
|
y_offset += line_height + section_spacing;
|
||||||
|
|
||||||
|
// Main VRAM usage with percentage
|
||||||
|
painter.setFont(value_font);
|
||||||
|
QColor vram_color = GetVramColor(current_vram_data.vram_percentage);
|
||||||
|
painter.setPen(vram_color);
|
||||||
|
|
||||||
|
QString vram_text = QString::fromUtf8("%1 / %2 (%3%)")
|
||||||
|
.arg(FormatMemorySize(current_vram_data.used_vram))
|
||||||
|
.arg(FormatMemorySize(current_vram_data.total_vram))
|
||||||
|
.arg(FormatPercentage(current_vram_data.vram_percentage));
|
||||||
|
|
||||||
|
painter.drawText(section_padding, y_offset, vram_text);
|
||||||
|
y_offset += line_height + section_spacing;
|
||||||
|
|
||||||
|
// Memory breakdown
|
||||||
|
painter.setFont(small_font);
|
||||||
|
painter.setPen(secondary_text_color);
|
||||||
|
|
||||||
|
QString buffer_text = QString::fromUtf8("Buffers: %1").arg(FormatMemorySize(current_vram_data.buffer_memory));
|
||||||
|
painter.drawText(section_padding, y_offset, buffer_text);
|
||||||
|
y_offset += line_height - 2;
|
||||||
|
|
||||||
|
QString texture_text = QString::fromUtf8("Textures: %1").arg(FormatMemorySize(current_vram_data.texture_memory));
|
||||||
|
painter.drawText(section_padding, y_offset, texture_text);
|
||||||
|
y_offset += line_height - 2;
|
||||||
|
|
||||||
|
QString staging_text = QString::fromUtf8("Staging: %1").arg(FormatMemorySize(current_vram_data.staging_memory));
|
||||||
|
painter.drawText(section_padding, y_offset, staging_text);
|
||||||
|
y_offset += line_height + section_spacing;
|
||||||
|
|
||||||
|
// VRAM mode indicator
|
||||||
|
painter.setPen(secondary_text_color);
|
||||||
|
QString mode_text;
|
||||||
|
switch (Settings::values.vram_usage_mode.GetValue()) {
|
||||||
|
case Settings::VramUsageMode::Conservative:
|
||||||
|
mode_text = QString::fromUtf8("Mode: Conservative");
|
||||||
|
break;
|
||||||
|
case Settings::VramUsageMode::Aggressive:
|
||||||
|
mode_text = QString::fromUtf8("Mode: Aggressive");
|
||||||
|
break;
|
||||||
|
case Settings::VramUsageMode::HighEnd:
|
||||||
|
mode_text = QString::fromUtf8("Mode: High-End GPU");
|
||||||
|
break;
|
||||||
|
case Settings::VramUsageMode::Insane:
|
||||||
|
mode_text = QString::fromUtf8("Mode: Insane");
|
||||||
|
painter.setPen(leak_warning_color);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode_text = QString::fromUtf8("Mode: Unknown");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
painter.drawText(section_padding, y_offset, mode_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::DrawVramGraph(QPainter& painter) {
|
||||||
|
if (vram_usage_history.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int graph_padding = 16;
|
||||||
|
const int graph_y = height() - 70;
|
||||||
|
const int graph_width = width() - (graph_padding * 2);
|
||||||
|
const int local_graph_height = 50;
|
||||||
|
|
||||||
|
// Draw graph background
|
||||||
|
QRect graph_rect(graph_padding, graph_y, graph_width, local_graph_height);
|
||||||
|
QPainterPath graph_path;
|
||||||
|
graph_path.addRoundedRect(graph_rect, 4, 4);
|
||||||
|
painter.fillPath(graph_path, graph_background_color);
|
||||||
|
|
||||||
|
// Draw grid lines
|
||||||
|
painter.setPen(QPen(graph_grid_color, 1));
|
||||||
|
for (int i = 0; i <= 4; ++i) {
|
||||||
|
int y = graph_y + (i * local_graph_height / 4);
|
||||||
|
painter.drawLine(graph_padding, y, graph_padding + graph_width, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw percentage labels on the right
|
||||||
|
painter.setFont(small_font);
|
||||||
|
painter.setPen(secondary_text_color);
|
||||||
|
for (int i = 0; i <= 4; ++i) {
|
||||||
|
int percentage = 100 - (i * 25);
|
||||||
|
int y = graph_y + (i * local_graph_height / 4) + 4;
|
||||||
|
painter.drawText(graph_padding + graph_width + 8, y, QString::number(percentage) + QStringLiteral("%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw VRAM usage line
|
||||||
|
if (vram_usage_history.size() > 1) {
|
||||||
|
painter.setPen(QPen(graph_line_color, 2));
|
||||||
|
|
||||||
|
QPainterPath line_path;
|
||||||
|
for (size_t i = 0; i < vram_usage_history.size(); ++i) {
|
||||||
|
double x = graph_padding + (static_cast<double>(i) / (vram_usage_history.size() - 1)) * graph_width;
|
||||||
|
double y = graph_y + local_graph_height - (vram_usage_history[i] / 100.0) * local_graph_height;
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
line_path.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
line_path.lineTo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
painter.drawPath(line_path);
|
||||||
|
|
||||||
|
// Draw fill under the line
|
||||||
|
line_path.lineTo(graph_padding + graph_width, graph_y + local_graph_height);
|
||||||
|
line_path.lineTo(graph_padding, graph_y + local_graph_height);
|
||||||
|
line_path.closeSubpath();
|
||||||
|
|
||||||
|
painter.fillPath(line_path, graph_fill_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::DrawLeakWarning(QPainter& painter) {
|
||||||
|
const int warning_y = height() - 25;
|
||||||
|
|
||||||
|
// Draw warning background
|
||||||
|
QRect warning_rect(padding, warning_y, width() - (padding * 2), 20);
|
||||||
|
QPainterPath warning_path;
|
||||||
|
warning_path.addRoundedRect(warning_rect, 3, 3);
|
||||||
|
painter.fillPath(warning_path, QColor(255, 152, 0, 60));
|
||||||
|
|
||||||
|
// Draw warning text
|
||||||
|
painter.setFont(warning_font);
|
||||||
|
painter.setPen(leak_warning_color);
|
||||||
|
QString warning_text = QString::fromUtf8("⚠ VRAM Leak Detected (+%1 MB)")
|
||||||
|
.arg(current_vram_data.leak_increase_mb);
|
||||||
|
painter.drawText(warning_rect, Qt::AlignCenter, warning_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::resizeEvent(QResizeEvent* event) {
|
||||||
|
QWidget::resizeEvent(event);
|
||||||
|
UpdatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::mousePressEvent(QMouseEvent* event) {
|
||||||
|
if (event->button() == Qt::LeftButton) {
|
||||||
|
is_dragging = true;
|
||||||
|
drag_start_pos = event->globalPos();
|
||||||
|
widget_start_pos = pos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||||
|
if (is_dragging) {
|
||||||
|
QPoint delta = event->globalPos() - drag_start_pos;
|
||||||
|
move(widget_start_pos + delta);
|
||||||
|
has_been_moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
|
Q_UNUSED(event)
|
||||||
|
is_dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::UpdateVramStats() {
|
||||||
|
if (!main_window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
current_vram_data.total_vram = main_window->GetTotalVram();
|
||||||
|
current_vram_data.used_vram = main_window->GetUsedVram();
|
||||||
|
current_vram_data.buffer_memory = main_window->GetBufferMemoryUsage();
|
||||||
|
current_vram_data.texture_memory = main_window->GetTextureMemoryUsage();
|
||||||
|
current_vram_data.staging_memory = main_window->GetStagingMemoryUsage();
|
||||||
|
|
||||||
|
if (current_vram_data.total_vram > 0) {
|
||||||
|
current_vram_data.vram_percentage = (static_cast<double>(current_vram_data.used_vram) / current_vram_data.total_vram) * 100.0;
|
||||||
|
current_vram_data.available_vram = current_vram_data.total_vram - current_vram_data.used_vram;
|
||||||
|
} else {
|
||||||
|
current_vram_data.vram_percentage = 0.0;
|
||||||
|
current_vram_data.available_vram = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leak detection
|
||||||
|
frame_counter++;
|
||||||
|
if (frame_counter % 10 == 0) { // Check every 10 seconds
|
||||||
|
if (current_vram_data.used_vram > last_vram_usage + (50 * 1024 * 1024)) { // 50MB increase
|
||||||
|
current_vram_data.leak_detected = true;
|
||||||
|
current_vram_data.leak_increase_mb = (current_vram_data.used_vram - last_vram_usage) / (1024 * 1024);
|
||||||
|
} else {
|
||||||
|
current_vram_data.leak_detected = false;
|
||||||
|
current_vram_data.leak_increase_mb = 0;
|
||||||
|
}
|
||||||
|
last_vram_usage = current_vram_data.used_vram;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddVramUsage(current_vram_data.vram_percentage);
|
||||||
|
|
||||||
|
if (!has_been_moved) {
|
||||||
|
UpdatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
} catch (...) {
|
||||||
|
// Ignore exceptions during VRAM access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor VramOverlay::GetVramColor(double percentage) const {
|
||||||
|
if (percentage < 70.0) {
|
||||||
|
return vram_safe_color;
|
||||||
|
} else if (percentage < 90.0) {
|
||||||
|
return vram_warning_color;
|
||||||
|
} else {
|
||||||
|
return vram_danger_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VramOverlay::FormatMemorySize(u64 bytes) const {
|
||||||
|
if (bytes >= 1024 * 1024 * 1024) {
|
||||||
|
return QString::number(static_cast<double>(bytes) / (1024.0 * 1024.0 * 1024.0), 'f', 1) + QStringLiteral(" GB");
|
||||||
|
} else if (bytes >= 1024 * 1024) {
|
||||||
|
return QString::number(static_cast<double>(bytes) / (1024.0 * 1024.0), 'f', 1) + QStringLiteral(" MB");
|
||||||
|
} else if (bytes >= 1024) {
|
||||||
|
return QString::number(static_cast<double>(bytes) / 1024.0, 'f', 1) + QStringLiteral(" KB");
|
||||||
|
} else {
|
||||||
|
return QString::number(bytes) + QStringLiteral(" B");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VramOverlay::FormatPercentage(double percentage) const {
|
||||||
|
return QString::number(percentage, 'f', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VramOverlay::AddVramUsage(double percentage) {
|
||||||
|
vram_usage_history.push_back(percentage);
|
||||||
|
|
||||||
|
if (vram_usage_history.size() > MAX_VRAM_HISTORY) {
|
||||||
|
vram_usage_history.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update min/max for scaling
|
||||||
|
min_vram_usage = *std::min_element(vram_usage_history.begin(), vram_usage_history.end());
|
||||||
|
max_vram_usage = *std::max_element(vram_usage_history.begin(), vram_usage_history.end());
|
||||||
|
|
||||||
|
// Add some padding to the range
|
||||||
|
double range = max_vram_usage - min_vram_usage;
|
||||||
|
if (range < 10.0) {
|
||||||
|
range = 10.0;
|
||||||
|
}
|
||||||
|
min_vram_usage = std::max(0.0, min_vram_usage - range * 0.1);
|
||||||
|
max_vram_usage = std::min(100.0, max_vram_usage + range * 0.1);
|
||||||
|
}
|
||||||
106
src/citron/util/vram_overlay.h
Normal file
106
src/citron/util/vram_overlay.h
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
struct VramUsageData {
|
||||||
|
u64 total_vram = 0;
|
||||||
|
u64 used_vram = 0;
|
||||||
|
u64 buffer_memory = 0;
|
||||||
|
u64 texture_memory = 0;
|
||||||
|
u64 staging_memory = 0;
|
||||||
|
u64 available_vram = 0;
|
||||||
|
bool leak_detected = false;
|
||||||
|
u64 leak_increase_mb = 0;
|
||||||
|
u32 cleanup_count = 0;
|
||||||
|
double vram_percentage = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VramOverlay : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit VramOverlay(GMainWindow* parent);
|
||||||
|
~VramOverlay() 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 UpdateVramStats();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdatePosition();
|
||||||
|
void DrawVramInfo(QPainter& painter);
|
||||||
|
void DrawVramGraph(QPainter& painter);
|
||||||
|
void DrawLeakWarning(QPainter& painter);
|
||||||
|
QColor GetVramColor(double percentage) const;
|
||||||
|
QString FormatMemorySize(u64 bytes) const;
|
||||||
|
QString FormatPercentage(double percentage) const;
|
||||||
|
void AddVramUsage(double percentage);
|
||||||
|
|
||||||
|
GMainWindow* main_window;
|
||||||
|
QTimer update_timer;
|
||||||
|
|
||||||
|
// VRAM data
|
||||||
|
VramUsageData current_vram_data;
|
||||||
|
u64 last_vram_usage = 0;
|
||||||
|
u32 frame_counter = 0;
|
||||||
|
|
||||||
|
// VRAM graph data
|
||||||
|
static constexpr size_t MAX_VRAM_HISTORY = 120; // 2 seconds at 60 FPS
|
||||||
|
std::deque<double> vram_usage_history;
|
||||||
|
double min_vram_usage = 0.0;
|
||||||
|
double max_vram_usage = 100.0;
|
||||||
|
|
||||||
|
// Display settings
|
||||||
|
bool is_visible = false;
|
||||||
|
bool is_dragging = false;
|
||||||
|
bool has_been_moved = false;
|
||||||
|
QPoint drag_start_pos;
|
||||||
|
QPoint widget_start_pos;
|
||||||
|
QFont title_font;
|
||||||
|
QFont value_font;
|
||||||
|
QFont small_font;
|
||||||
|
QFont warning_font;
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
QColor background_color;
|
||||||
|
QColor border_color;
|
||||||
|
QColor text_color;
|
||||||
|
QColor secondary_text_color;
|
||||||
|
QColor vram_safe_color; // Green for < 70%
|
||||||
|
QColor vram_warning_color; // Yellow for 70-90%
|
||||||
|
QColor vram_danger_color; // Red for > 90%
|
||||||
|
QColor leak_warning_color; // Orange for leak detection
|
||||||
|
QColor graph_background_color;
|
||||||
|
QColor graph_grid_color;
|
||||||
|
QColor graph_line_color;
|
||||||
|
QColor graph_fill_color;
|
||||||
|
|
||||||
|
// Layout constants
|
||||||
|
static constexpr int padding = 12;
|
||||||
|
static constexpr int corner_radius = 8;
|
||||||
|
static constexpr int border_width = 1;
|
||||||
|
static constexpr int graph_height = 60;
|
||||||
|
static constexpr int warning_height = 30;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user