diff --git a/src/citron/CMakeLists.txt b/src/citron/CMakeLists.txt index 1eaff5b4b..7e4ece456 100644 --- a/src/citron/CMakeLists.txt +++ b/src/citron/CMakeLists.txt @@ -226,6 +226,8 @@ add_executable(citron util/overlay_dialog.ui util/performance_overlay.cpp util/performance_overlay.h + util/vram_overlay.cpp + util/vram_overlay.h util/sequence_dialog/sequence_dialog.cpp util/sequence_dialog/sequence_dialog.h util/url_request_interceptor.cpp diff --git a/src/citron/main.cpp b/src/citron/main.cpp index 9cfc57c94..17f9bd1c2 100644 --- a/src/citron/main.cpp +++ b/src/citron/main.cpp @@ -140,6 +140,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "util/overlay_dialog.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 "video_core/shader_notify.h" #include "citron/about_dialog.h" #include "citron/bootmanager.h" @@ -166,6 +168,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "citron/updater/updater_service.h" #include "citron/util/clickable_label.h" #include "citron/util/performance_overlay.h" +#include "citron/util/vram_overlay.h" #include "citron/vk_device_info.h" #ifdef CITRON_CRASH_DUMPS @@ -1080,6 +1083,9 @@ void GMainWindow::InitializeWidgets() { performance_overlay = new PerformanceOverlay(this); performance_overlay->hide(); + vram_overlay = new VramOverlay(this); + vram_overlay->hide(); + tas_label = new QLabel(); tas_label->setObjectName(QStringLiteral("TASlabel")); 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_Show_Status_Bar, QStringLiteral("Toggle Status Bar")); 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_Capture_Screenshot, QStringLiteral("Capture Screenshot")); LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true); @@ -1464,6 +1471,11 @@ void GMainWindow::RestoreUIState() { if (performance_overlay) { 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(); } @@ -1580,6 +1592,7 @@ void GMainWindow::ConnectMenuEvents() { 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_Show_Vram_Overlay, &GMainWindow::OnToggleVramOverlay); connect_menu(ui->action_Toggle_Grid_View, &GMainWindow::OnToggleGridView); 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 { if (!system || !system->IsPoweredOn()) { return 0.0; @@ -4440,6 +4459,112 @@ u32 GMainWindow::GetShadersBuilding() const { 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(&renderer); + if (vulkan_renderer) { + VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer(); + Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast(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(&renderer); + if (vulkan_renderer) { + VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer(); + Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast(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(&renderer); + if (vulkan_renderer) { + VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer(); + Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast(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(&renderer); + if (vulkan_renderer) { + VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer(); + Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast(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(&renderer); + if (vulkan_renderer) { + VideoCore::RasterizerInterface* rasterizer = vulkan_renderer->ReadRasterizer(); + Vulkan::RasterizerVulkan* vulkan_rasterizer = dynamic_cast(rasterizer); + if (vulkan_rasterizer) { + return vulkan_rasterizer->GetStagingMemoryUsage(); + } + } + } catch (...) { + // Ignore exceptions + } + return 0; +} + double GMainWindow::GetEmulationSpeed() const { if (!system || !system->IsPoweredOn()) { return 0.0; @@ -4814,6 +4939,7 @@ void GMainWindow::UpdateUISettings() { 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.show_vram_overlay = ui->action_Show_Vram_Overlay->isChecked(); UISettings::values.first_start = false; } diff --git a/src/citron/main.h b/src/citron/main.h index b61d2458d..30f1f069e 100644 --- a/src/citron/main.h +++ b/src/citron/main.h @@ -39,6 +39,7 @@ class LoadingScreen; class MicroProfileDialog; class OverlayDialog; class PerformanceOverlay; +class VramOverlay; class ProfilerWidget; // Forward declaration @@ -164,6 +165,7 @@ class GMainWindow : public QMainWindow { static const int max_recent_files_item = 10; friend class PerformanceOverlay; + friend class VramOverlay; enum { CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, @@ -396,6 +398,7 @@ private slots: void OnToggleGridView(); void OnToggleStatusBar(); void OnTogglePerformanceOverlay(); + void OnToggleVramOverlay(); void OnDisplayTitleBars(bool); // Performance overlay access methods @@ -403,6 +406,13 @@ private slots: double GetCurrentFrameTime() const; u32 GetShadersBuilding() 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 ToggleFullscreen(); bool UsingExclusiveFullscreen(); @@ -503,6 +513,7 @@ private: QTimer shutdown_timer; OverlayDialog* shutdown_dialog{}; PerformanceOverlay* performance_overlay{}; + VramOverlay* vram_overlay{}; GameListPlaceholder* game_list_placeholder; diff --git a/src/citron/main.ui b/src/citron/main.ui index 4d19c923c..5103ac176 100644 --- a/src/citron/main.ui +++ b/src/citron/main.ui @@ -125,6 +125,7 @@ + @@ -307,6 +308,17 @@ Show Performance Overlay + + + true + + + Show &VRAM Monitor + + + Show VRAM Monitor + + true diff --git a/src/citron/uisettings.h b/src/citron/uisettings.h index a931d0268..19b2cfd71 100644 --- a/src/citron/uisettings.h +++ b/src/citron/uisettings.h @@ -102,6 +102,7 @@ struct Values { Setting show_filter_bar{linkage, true, "showFilterBar", Category::Ui}; Setting show_status_bar{linkage, true, "showStatusBar", Category::Ui}; Setting show_performance_overlay{linkage, false, "showPerformanceOverlay", Category::Ui}; + Setting show_vram_overlay{linkage, false, "showVramOverlay", Category::Ui}; SwitchableSetting confirm_before_stopping{linkage, ConfirmStop::Ask_Always, diff --git a/src/citron/util/vram_overlay.cpp b/src/citron/util/vram_overlay.cpp new file mode 100644 index 000000000..080a2c6b5 --- /dev/null +++ b/src/citron/util/vram_overlay.cpp @@ -0,0 +1,375 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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(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(bytes) / (1024.0 * 1024.0 * 1024.0), 'f', 1) + QStringLiteral(" GB"); + } else if (bytes >= 1024 * 1024) { + return QString::number(static_cast(bytes) / (1024.0 * 1024.0), 'f', 1) + QStringLiteral(" MB"); + } else if (bytes >= 1024) { + return QString::number(static_cast(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); +} \ No newline at end of file diff --git a/src/citron/util/vram_overlay.h b/src/citron/util/vram_overlay.h new file mode 100644 index 000000000..71db506b6 --- /dev/null +++ b/src/citron/util/vram_overlay.h @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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; +};