diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt
index 47df65334b..2412609804 100644
--- a/Source/Core/DolphinQt/CMakeLists.txt
+++ b/Source/Core/DolphinQt/CMakeLists.txt
@@ -362,6 +362,8 @@ add_executable(dolphin-emu
Settings/GeneralPane.h
Settings/InterfacePane.cpp
Settings/InterfacePane.h
+ Settings/OnScreenDisplayPane.cpp
+ Settings/OnScreenDisplayPane.h
Settings/PathPane.cpp
Settings/PathPane.h
Settings/USBDevicePicker.cpp
diff --git a/Source/Core/DolphinQt/Config/SettingsWindow.cpp b/Source/Core/DolphinQt/Config/SettingsWindow.cpp
index a1279c5eb5..8efae30cee 100644
--- a/Source/Core/DolphinQt/Config/SettingsWindow.cpp
+++ b/Source/Core/DolphinQt/Config/SettingsWindow.cpp
@@ -22,6 +22,7 @@
#include "DolphinQt/Settings/GameCubePane.h"
#include "DolphinQt/Settings/GeneralPane.h"
#include "DolphinQt/Settings/InterfacePane.h"
+#include "DolphinQt/Settings/OnScreenDisplayPane.h"
#include "DolphinQt/Settings/PathPane.h"
#include "DolphinQt/Settings/WiiPane.h"
@@ -129,6 +130,7 @@ SettingsWindow::SettingsWindow(MainWindow* parent) : StackedSettingsWindow{paren
AddPane(new GraphicsPane{parent, nullptr}, tr("Graphics"));
AddWrappedPane(new ControllersPane, tr("Controllers"));
AddWrappedPane(new InterfacePane, tr("Interface"));
+ AddWrappedPane(new OnScreenDisplayPane, tr("On-Screen Display"));
AddWrappedPane(new AudioPane, tr("Audio"));
AddWrappedPane(new PathPane, tr("Paths"));
AddWrappedPane(new GameCubePane, tr("GameCube"));
diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj
index 596de91a53..eb548ae0ad 100644
--- a/Source/Core/DolphinQt/DolphinQt.vcxproj
+++ b/Source/Core/DolphinQt/DolphinQt.vcxproj
@@ -218,6 +218,7 @@
+
@@ -268,6 +269,7 @@
+
diff --git a/Source/Core/DolphinQt/Settings/OnScreenDisplayPane.cpp b/Source/Core/DolphinQt/Settings/OnScreenDisplayPane.cpp
new file mode 100644
index 0000000000..5076eba710
--- /dev/null
+++ b/Source/Core/DolphinQt/Settings/OnScreenDisplayPane.cpp
@@ -0,0 +1,248 @@
+// Copyright 2025 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "DolphinQt/Settings/OnScreenDisplayPane.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "Core/Config/GraphicsSettings.h"
+#include "Core/Config/MainSettings.h"
+
+#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
+#include "DolphinQt/Config/ConfigControls/ConfigInteger.h"
+
+OnScreenDisplayPane::OnScreenDisplayPane(QWidget* parent) : QWidget(parent)
+{
+ CreateLayout();
+ ConnectLayout();
+ AddDescriptions();
+}
+
+void OnScreenDisplayPane::CreateLayout()
+{
+ // General
+ auto* general_box = new QGroupBox(tr("General"));
+ auto* general_layout = new QGridLayout();
+ general_box->setLayout(general_layout);
+
+ m_enable_osd = new ConfigBool(tr("Show Messages"), Config::MAIN_OSD_MESSAGES);
+ m_font_size = new ConfigInteger(12, 40, Config::MAIN_OSD_FONT_SIZE);
+
+ general_layout->addWidget(m_enable_osd, 0, 0);
+ general_layout->addWidget(new QLabel(tr("Font Size:")), 1, 0);
+ general_layout->addWidget(m_font_size, 1, 1);
+
+ // Performance
+ auto* performance_box = new QGroupBox(tr("Performance Statistics"));
+ auto* performance_layout = new QGridLayout();
+ performance_box->setLayout(performance_layout);
+
+ m_show_fps = new ConfigBool(tr("Show FPS"), Config::GFX_SHOW_FPS);
+ m_show_ftimes = new ConfigBool(tr("Show Frame Times"), Config::GFX_SHOW_FTIMES);
+ m_show_vps = new ConfigBool(tr("Show VPS"), Config::GFX_SHOW_VPS);
+ m_show_vtimes = new ConfigBool(tr("Show VBlank Times"), Config::GFX_SHOW_VTIMES);
+ m_show_speed = new ConfigBool(tr("Show % Speed"), Config::GFX_SHOW_SPEED);
+ m_speed_colors = new ConfigBool(tr("Show Speed Colors"), Config::GFX_SHOW_SPEED_COLORS);
+ m_show_graph = new ConfigBool(tr("Show Performance Graphs"), Config::GFX_SHOW_GRAPHS);
+ m_graph_update_label = new QLabel(tr("Graph Update Rate (ms):"));
+ m_graph_update_rate = new ConfigInteger(0, 10000, Config::GFX_PERF_SAMP_WINDOW, 100);
+ m_graph_update_rate->SetTitle(tr("Performance Sample Window (ms)"));
+
+ performance_layout->addWidget(m_show_fps, 0, 0);
+ performance_layout->addWidget(m_show_ftimes, 0, 1);
+ performance_layout->addWidget(m_show_vps, 1, 0);
+ performance_layout->addWidget(m_show_vtimes, 1, 1);
+ performance_layout->addWidget(m_show_speed, 2, 0);
+ performance_layout->addWidget(m_speed_colors, 2, 1);
+ performance_layout->addWidget(m_show_graph, 3, 0);
+ performance_layout->addWidget(m_graph_update_label, 4, 0);
+ performance_layout->addWidget(m_graph_update_rate, 4, 1);
+
+ // Movie
+ auto* movie_box = new QGroupBox(tr("Movie Window"));
+ auto* movie_layout = new QGridLayout();
+ movie_box->setLayout(movie_layout);
+
+ m_rerecord_counter =
+ new ConfigBool(tr("Show Rerecord Counter"), Config::MAIN_MOVIE_SHOW_RERECORD);
+ m_lag_counter = new ConfigBool(tr("Show Lag Counter"), Config::MAIN_SHOW_LAG);
+ m_frame_counter = new ConfigBool(tr("Show Frame Counter"), Config::MAIN_SHOW_FRAME_COUNT);
+ m_input_display = new ConfigBool(tr("Show Input Display"), Config::MAIN_MOVIE_SHOW_INPUT_DISPLAY);
+ m_system_clock = new ConfigBool(tr("Show System Clock"), Config::MAIN_MOVIE_SHOW_RTC);
+
+ movie_layout->addWidget(m_rerecord_counter, 0, 0);
+ movie_layout->addWidget(m_lag_counter, 0, 1);
+ movie_layout->addWidget(m_frame_counter, 1, 0);
+ movie_layout->addWidget(m_input_display, 1, 1);
+ movie_layout->addWidget(m_system_clock, 2, 0);
+
+ // NetPlay
+ auto* netplay_box = new QGroupBox(tr("Netplay"));
+ auto* netplay_layout = new QGridLayout();
+ netplay_box->setLayout(netplay_layout);
+
+ m_show_ping = new ConfigBool(tr("Show NetPlay Ping"), Config::GFX_SHOW_NETPLAY_PING);
+ m_show_chat = new ConfigBool(tr("Show NetPlay Chat"), Config::GFX_SHOW_NETPLAY_MESSAGES);
+
+ netplay_layout->addWidget(m_show_ping, 0, 0);
+ netplay_layout->addWidget(m_show_chat, 0, 1);
+
+ // Debug
+ auto* debug_box = new QGroupBox(tr("Debug"));
+ auto* debug_layout = new QGridLayout();
+ debug_box->setLayout(debug_layout);
+
+ m_show_statistics = new ConfigBool(tr("Show Statistics"), Config::GFX_OVERLAY_STATS);
+ m_show_proj_statistics =
+ new ConfigBool(tr("Show Projection Statistics"), Config::GFX_OVERLAY_PROJ_STATS);
+
+ debug_layout->addWidget(m_show_statistics, 0, 0);
+ debug_layout->addWidget(m_show_proj_statistics, 0, 1);
+
+ // Stack GroupBoxes
+ auto* main_layout = new QVBoxLayout;
+ main_layout->addWidget(general_box);
+ main_layout->addWidget(performance_box);
+ main_layout->addWidget(movie_box);
+ main_layout->addWidget(netplay_box);
+ main_layout->addWidget(debug_box);
+ main_layout->addStretch();
+ setLayout(main_layout);
+}
+
+void OnScreenDisplayPane::ConnectLayout()
+{
+ // Disable graph options when graph is not visible.
+ m_graph_update_rate->setEnabled(m_show_graph->isChecked());
+ m_graph_update_label->setEnabled(m_show_graph->isChecked());
+ connect(m_show_graph, &QCheckBox::toggled, this, [this](bool checked) {
+ m_graph_update_rate->setEnabled(checked);
+ m_graph_update_label->setEnabled(checked);
+ });
+
+ // Disable movie window options when window is closed.
+ auto enable_movie_items = [this](bool checked) {
+ for (auto* widget :
+ {m_rerecord_counter, m_frame_counter, m_lag_counter, m_system_clock, m_input_display})
+ {
+ widget->setEnabled(checked);
+ }
+ };
+
+ enable_movie_items(m_movie_window->isChecked());
+ connect(m_movie_window, &QCheckBox::toggled, this, [this, enable_movie_items](bool checked) {
+ enable_movie_items(m_movie_window->isChecked());
+ });
+}
+
+void OnScreenDisplayPane::AddDescriptions()
+{
+ static constexpr char TR_ENABLE_OSD_DESCRIPTION[] =
+ QT_TR_NOOP("Shows on-screen display messages over the render window. These messages "
+ "disappear after several seconds."
+ "
If unsure, leave this checked.");
+ static const char TR_OSD_FONT_SIZE_DESCRIPTION[] = QT_TR_NOOP(
+ "Changes the font size of the On-Screen Display. Affects features such as performance "
+ "statistics, frame counter, and netplay chat.
If "
+ "unsure, leave this at 13.");
+
+ static const char TR_SHOW_FPS_DESCRIPTION[] =
+ QT_TR_NOOP("Shows the number of distinct frames rendered per second as a measure of "
+ "visual smoothness.
If unsure, leave this "
+ "unchecked.");
+ static const char TR_SHOW_FTIMES_DESCRIPTION[] =
+ QT_TR_NOOP("Shows the average time in ms between each distinct rendered frame alongside "
+ "the standard deviation.
If unsure, leave this "
+ "unchecked.");
+ static const char TR_SHOW_VPS_DESCRIPTION[] =
+ QT_TR_NOOP("Shows the number of frames rendered per second as a measure of "
+ "emulation speed.
If unsure, leave this "
+ "unchecked.");
+ static const char TR_SHOW_VTIMES_DESCRIPTION[] =
+ QT_TR_NOOP("Shows the average time in ms between each rendered frame alongside "
+ "the standard deviation.
If unsure, leave this "
+ "unchecked.");
+ static const char TR_SHOW_GRAPHS_DESCRIPTION[] =
+ QT_TR_NOOP("Shows frametime graph along with statistics as a representation of "
+ "emulation performance.
If unsure, leave this "
+ "unchecked.");
+ static const char TR_SHOW_SPEED_DESCRIPTION[] =
+ QT_TR_NOOP("Shows the % speed of emulation compared to full speed."
+ "
If unsure, leave this "
+ "unchecked.");
+ static const char TR_SHOW_SPEED_COLORS_DESCRIPTION[] =
+ QT_TR_NOOP("Changes the color of the FPS counter depending on emulation speed."
+ "
If unsure, leave this "
+ "checked.");
+ static const char TR_PERF_SAMP_WINDOW_DESCRIPTION[] =
+ QT_TR_NOOP("The amount of time the FPS and VPS counters will sample over."
+ "
The higher the value, the more stable the FPS/VPS counter will be, "
+ "but the slower it will be to update."
+ "
If unsure, leave this "
+ "at 1000ms.");
+ static const char TR_LOG_RENDERTIME_DESCRIPTION[] = QT_TR_NOOP(
+ "Logs the render time of every frame to User/Logs/render_time.txt.
Use this "
+ "feature to measure Dolphin's performance.
If "
+ "unsure, leave this unchecked.");
+
+ static const char TR_SHOW_NETPLAY_PING_DESCRIPTION[] = QT_TR_NOOP(
+ "Shows the player's maximum ping while playing on "
+ "NetPlay.
If unsure, leave this unchecked.");
+ static const char TR_SHOW_NETPLAY_MESSAGES_DESCRIPTION[] =
+ QT_TR_NOOP("Shows chat messages, buffer changes, and desync alerts "
+ "while playing NetPlay.
If unsure, leave "
+ "this unchecked.");
+
+ static const char TR_RERECORD_COUNTER_DESCRIPTION[] =
+ QT_TR_NOOP("Shows how many times the input recording has been overwritten by using "
+ "savestates.
If unsure, leave "
+ "this unchecked.");
+ static const char TR_LAG_COUNTER_DESCRIPTION[] = QT_TR_NOOP(
+ "Shows how many frames have occured without controller inputs being checked. Resets to 1 "
+ "when inputs are processed.
If unsure, leave "
+ "this unchecked.");
+ static const char TR_FRAME_COUNTER_DESCRIPTION[] =
+ QT_TR_NOOP("Shows how many frames have passed.
If unsure, leave "
+ "this unchecked.");
+ static const char TR_INPUT_DISPLAY_DESCRIPTION[] = QT_TR_NOOP(
+ "Shows the controls currently being input.
If unsure, leave "
+ "this unchecked.");
+ static const char TR_SYSTEM_CLOCK_DESCRIPTION[] =
+ QT_TR_NOOP("Shows current system time.
If unsure, leave "
+ "this unchecked.");
+
+ static const char TR_SHOW_STATS_DESCRIPTION[] =
+ QT_TR_NOOP("Shows various rendering statistics.
If unsure, "
+ "leave this unchecked.");
+ static const char TR_SHOW_PROJ_STATS_DESCRIPTION[] =
+ QT_TR_NOOP("Shows various projection statistics.
If unsure, "
+ "leave this unchecked.");
+
+ m_enable_osd->SetDescription(tr(TR_ENABLE_OSD_DESCRIPTION));
+ m_font_size->SetDescription(tr(TR_OSD_FONT_SIZE_DESCRIPTION));
+
+ m_show_fps->SetDescription(tr(TR_SHOW_FPS_DESCRIPTION));
+ m_show_ftimes->SetDescription(tr(TR_SHOW_FTIMES_DESCRIPTION));
+ m_show_vps->SetDescription(tr(TR_SHOW_VPS_DESCRIPTION));
+ m_show_vtimes->SetDescription(tr(TR_SHOW_VTIMES_DESCRIPTION));
+ m_show_graph->SetDescription(tr(TR_SHOW_GRAPHS_DESCRIPTION));
+ m_show_speed->SetDescription(tr(TR_SHOW_SPEED_DESCRIPTION));
+ m_graph_update_rate->SetDescription(tr(TR_PERF_SAMP_WINDOW_DESCRIPTION));
+ m_speed_colors->SetDescription(tr(TR_SHOW_SPEED_COLORS_DESCRIPTION));
+
+ m_show_ping->SetDescription(tr(TR_SHOW_NETPLAY_PING_DESCRIPTION));
+ m_show_chat->SetDescription(tr(TR_SHOW_NETPLAY_MESSAGES_DESCRIPTION));
+
+ m_rerecord_counter->SetDescription(tr(TR_RERECORD_COUNTER_DESCRIPTION));
+ m_lag_counter->SetDescription(tr(TR_LAG_COUNTER_DESCRIPTION));
+ m_frame_counter->SetDescription(tr(TR_FRAME_COUNTER_DESCRIPTION));
+ m_input_display->SetDescription(tr(TR_INPUT_DISPLAY_DESCRIPTION));
+ m_system_clock->SetDescription(tr(TR_SYSTEM_CLOCK_DESCRIPTION));
+
+ m_show_statistics->SetDescription(tr(TR_SHOW_STATS_DESCRIPTION));
+ m_show_proj_statistics->SetDescription(tr(TR_SHOW_PROJ_STATS_DESCRIPTION));
+}
diff --git a/Source/Core/DolphinQt/Settings/OnScreenDisplayPane.h b/Source/Core/DolphinQt/Settings/OnScreenDisplayPane.h
new file mode 100644
index 0000000000..89bbadef02
--- /dev/null
+++ b/Source/Core/DolphinQt/Settings/OnScreenDisplayPane.h
@@ -0,0 +1,51 @@
+// Copyright 2025 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+
+class QLabel;
+class ConfigBool;
+class ConfigInteger;
+
+class OnScreenDisplayPane final : public QWidget
+{
+public:
+ explicit OnScreenDisplayPane(QWidget* parent = nullptr);
+
+private:
+ void CreateLayout();
+ void ConnectLayout();
+ void AddDescriptions();
+
+ // General
+ ConfigBool* m_enable_osd;
+ ConfigInteger* m_font_size;
+
+ // Performance
+ ConfigBool* m_show_fps;
+ ConfigBool* m_show_ftimes;
+ ConfigBool* m_show_vps;
+ ConfigBool* m_show_vtimes;
+ ConfigBool* m_show_graph;
+ ConfigBool* m_show_speed;
+ ConfigBool* m_speed_colors;
+ QLabel* m_graph_update_label;
+ ConfigInteger* m_graph_update_rate;
+
+ // Movie window
+ ConfigBool* m_rerecord_counter;
+ ConfigBool* m_lag_counter;
+ ConfigBool* m_frame_counter;
+ ConfigBool* m_input_display;
+ ConfigBool* m_system_clock;
+
+ // Netplay
+ ConfigBool* m_show_ping;
+ ConfigBool* m_show_chat;
+
+ // Debug
+ ConfigBool* m_show_statistics;
+ ConfigBool* m_show_proj_statistics;
+};