Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/duckstation-qt/consolesettingswidget.cpp
4802 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "consolesettingswidget.h"
5
#include "qtutils.h"
6
#include "settingswindow.h"
7
#include "settingwidgetbinder.h"
8
9
#include "core/game_database.h"
10
#include "core/system.h"
11
12
#include "util/cd_image.h"
13
14
#include <QtWidgets/QMessageBox>
15
#include <QtWidgets/QPushButton>
16
17
#include "moc_consolesettingswidget.cpp"
18
19
static constexpr const int CDROM_SPEEDUP_VALUES[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0};
20
21
ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsWindow* dialog, QWidget* parent)
22
: QWidget(parent), m_dialog(dialog)
23
{
24
SettingsInterface* sif = dialog->getSettingsInterface();
25
26
m_ui.setupUi(this);
27
28
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName,
29
&Settings::GetConsoleRegionName, &Settings::GetConsoleRegionDisplayName,
30
Settings::DEFAULT_CONSOLE_REGION, ConsoleRegion::Count,
31
&QtUtils::GetIconForRegion);
32
SettingWidgetBinder::BindWidgetToEnumSetting(
33
sif, m_ui.forceVideoTiming, "GPU", "ForceVideoTiming", &Settings::ParseForceVideoTimingName,
34
&Settings::GetForceVideoTimingName, &Settings::GetForceVideoTimingDisplayName,
35
Settings::DEFAULT_FORCE_VIDEO_TIMING_MODE, ForceVideoTimingMode::Count, +[](ForceVideoTimingMode mode) {
36
return QtUtils::GetIconForRegion(
37
(mode == ForceVideoTimingMode::Disabled) ?
38
ConsoleRegion::Auto :
39
((mode == ForceVideoTimingMode::NTSC) ? ConsoleRegion::NTSC_U : ConsoleRegion::PAL));
40
});
41
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.fastBoot, "BIOS", "PatchFastBoot", false);
42
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.fastForwardBoot, "BIOS", "FastForwardBoot", false);
43
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable8MBRAM, "Console", "Enable8MBRAM", false);
44
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.fastForwardMemoryCardAccess, "MemoryCards",
45
"FastForwardAccess", false);
46
connect(m_ui.fastBoot, &QCheckBox::checkStateChanged, this, &ConsoleSettingsWidget::onFastBootChanged);
47
onFastBootChanged();
48
49
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.cpuExecutionMode, "CPU", "ExecutionMode",
50
&Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName,
51
&Settings::GetCPUExecutionModeDisplayName,
52
Settings::DEFAULT_CPU_EXECUTION_MODE, CPUExecutionMode::Count);
53
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCPUClockSpeedControl, "CPU", "OverclockEnable", false);
54
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.recompilerICache, "CPU", "RecompilerICache", false);
55
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM", false);
56
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cdromAutoDiscChange, "CDROM", "AutoDiscChange", false);
57
58
if (!m_dialog->isPerGameSettings())
59
{
60
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cdromLoadImagePatches, "CDROM", "LoadImagePatches", false);
61
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cdromIgnoreDriveSubcode, "CDROM", "IgnoreHostSubcode",
62
false);
63
}
64
else
65
{
66
m_ui.cdromGridLayout->removeWidget(m_ui.cdromIgnoreDriveSubcode);
67
delete m_ui.cdromIgnoreDriveSubcode;
68
m_ui.cdromIgnoreDriveSubcode = nullptr;
69
m_ui.cdromGridLayout->removeWidget(m_ui.cdromLoadImagePatches);
70
delete m_ui.cdromLoadImagePatches;
71
m_ui.cdromLoadImagePatches = nullptr;
72
}
73
74
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromSeekSpeedup, "CDROM", "SeekSpeedup", 1,
75
CDROM_SPEEDUP_VALUES);
76
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromReadSpeedup, "CDROM", "ReadSpeedup", 1,
77
CDROM_SPEEDUP_VALUES);
78
79
dialog->registerWidgetHelp(m_ui.region, tr("Region"), tr("Auto-Detect"),
80
tr("Determines the emulated hardware type."));
81
dialog->registerWidgetHelp(
82
m_ui.forceVideoTiming, tr("Frame Rate"), tr("Auto-Detect"),
83
tr("Utilizes the chosen frame timing regardless of the active region. This feature can be used to force PAL games "
84
"to run at 60Hz and NTSC games to run at 50Hz. For most games which have a speed tied to the framerate, this "
85
"will result in the game running approximately 17% faster or slower. For variable frame rate games, it may not "
86
"affect the speed."));
87
m_dialog->registerWidgetHelp(m_ui.fastBoot, tr("Fast Boot"), tr("Unchecked"),
88
tr("Skips the boot animation. Safe to enable."));
89
m_dialog->registerWidgetHelp(m_ui.fastForwardBoot, tr("Fast Forward Boot"), tr("Unchecked"),
90
tr("Fast forwards through the early loading process when fast booting, saving time. "
91
"Results may vary between games."));
92
dialog->registerWidgetHelp(
93
m_ui.enable8MBRAM, tr("Enable 8MB RAM (Dev Console)"), tr("Unchecked"),
94
tr("Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles. Games have "
95
"to use a larger heap size for "
96
"this additional RAM to be usable. Titles which rely on memory mirrors may break, so it should only be used "
97
"with compatible mods."));
98
m_dialog->registerWidgetHelp(m_ui.fastForwardMemoryCardAccess, tr("Fast Forward Memory Card Access"), tr("Unchecked"),
99
tr("Fast forwards through memory card access, both loading and saving. Can reduce "
100
"waiting times in games that frequently access memory cards."));
101
102
dialog->registerWidgetHelp(m_ui.cpuExecutionMode, tr("Execution Mode"), tr("Recompiler (Fastest)"),
103
tr("Determines how the emulated CPU executes instructions."));
104
dialog->registerWidgetHelp(m_ui.enableCPUClockSpeedControl,
105
tr("Enable Clock Speed Control (Overclocking/Underclocking)"), tr("Unchecked"),
106
tr("When this option is chosen, the clock speed set below will be used."));
107
dialog->registerWidgetHelp(m_ui.cpuClockSpeed, tr("Overclocking Percentage"), tr("100%"),
108
tr("Selects the percentage of the normal clock speed the emulated hardware will run at."));
109
dialog->registerWidgetHelp(m_ui.recompilerICache, tr("Enable Recompiler ICache"), tr("Unchecked"),
110
tr("Simulates stalls in the recompilers when the emulated CPU would have to fetch "
111
"instructions into its cache. Makes games run closer to their console framerate, at a "
112
"small cost to performance. Interpreter mode always simulates the instruction cache."));
113
114
dialog->registerWidgetHelp(
115
m_ui.cdromReadSpeedup, tr("CD-ROM Read Speedup"), tr("None (Double Speed)"),
116
tr("Speeds up CD-ROM reads by the specified factor. Only applies to double-speed reads, and is ignored when audio "
117
"is playing. May improve loading speeds in some games, at the cost of breaking others."));
118
dialog->registerWidgetHelp(
119
m_ui.cdromSeekSpeedup, tr("CD-ROM Seek Speedup"), tr("None (Normal Speed)"),
120
tr("Reduces the simulated time for the CD-ROM sled to move to different areas of the disc. Can improve loading "
121
"times, but crash games which do not expect the CD-ROM to operate faster."));
122
dialog->registerWidgetHelp(
123
m_ui.cdromLoadImageToRAM, tr("Preload Image to RAM"), tr("Unchecked"),
124
tr("Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some "
125
"cases also eliminates stutter when games initiate audio track playback."));
126
dialog->registerWidgetHelp(m_ui.cdromLoadImagePatches, tr("Apply Image Patches"), tr("Unchecked"),
127
tr("Automatically applies patches to disc images when they are present in the same "
128
"directory. Currently only PPF patches are supported with this option."));
129
dialog->registerWidgetHelp(
130
m_ui.cdromAutoDiscChange, tr("Switch to Next Disc on Stop"), tr("Unchecked"),
131
tr("Automatically switches to the next disc in the game when the game stops the CD-ROM motor. No switch will occur "
132
"if the last disc in the game is already selected. <strong>Does not work for all games.</strong>"));
133
dialog->registerWidgetHelp(
134
m_ui.cdromIgnoreDriveSubcode, tr("Ignore Drive Subcode"), tr("Unchecked"),
135
tr("Ignores the subchannel provided by the drive when using physical discs, instead always generating subchannel "
136
"data. Won't work with libcrypt games, but can improve read reliability on some drives."));
137
138
m_ui.cpuClockSpeed->setEnabled(m_dialog->getEffectiveBoolValue("CPU", "OverclockEnable", false));
139
140
connect(m_ui.enableCPUClockSpeedControl, &QCheckBox::checkStateChanged, this,
141
&ConsoleSettingsWidget::onEnableCPUClockSpeedControlChecked);
142
connect(m_ui.cpuClockSpeed, &QSlider::valueChanged, this, &ConsoleSettingsWidget::onCPUClockSpeedValueChanged);
143
144
SettingWidgetBinder::SetAvailability(m_ui.fastBoot, !m_dialog->hasGameTrait(GameDatabase::Trait::ForceFullBoot));
145
SettingWidgetBinder::SetAvailability(m_ui.fastForwardBoot,
146
!m_dialog->hasGameTrait(GameDatabase::Trait::ForceFullBoot));
147
SettingWidgetBinder::SetAvailability(
148
m_ui.cpuExecutionMode, !m_dialog->hasGameTrait(GameDatabase::Trait::ForceInterpreter), m_ui.cpuExecutionModeLabel);
149
SettingWidgetBinder::SetAvailability(m_ui.cdromReadSpeedup,
150
!m_dialog->hasGameTrait(GameDatabase::Trait::DisableCDROMReadSpeedup),
151
m_ui.cdromReadSpeedupLabel);
152
SettingWidgetBinder::SetAvailability(m_ui.cdromSeekSpeedup,
153
!m_dialog->hasGameTrait(GameDatabase::Trait::DisableCDROMSeekSpeedup),
154
m_ui.cdromSeekSpeedupLabel);
155
SettingWidgetBinder::SetForceEnabled(m_ui.recompilerICache,
156
m_dialog->hasGameTrait(GameDatabase::Trait::ForceRecompilerICache));
157
158
calculateCPUClockValue();
159
}
160
161
ConsoleSettingsWidget::~ConsoleSettingsWidget() = default;
162
163
void ConsoleSettingsWidget::onFastBootChanged()
164
{
165
const bool fast_boot_enabled =
166
m_dialog->getEffectiveBoolValue("BIOS", "PatchFastBoot", Settings::DEFAULT_FAST_BOOT_VALUE);
167
m_ui.fastForwardBoot->setEnabled(fast_boot_enabled);
168
}
169
170
void ConsoleSettingsWidget::updateRecompilerICacheEnabled()
171
{
172
const CPUExecutionMode mode =
173
Settings::ParseCPUExecutionMode(
174
m_dialog
175
->getEffectiveStringValue("CPU", "ExecutionMode",
176
Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE))
177
.c_str())
178
.value_or(Settings::DEFAULT_CPU_EXECUTION_MODE);
179
m_ui.recompilerICache->setEnabled(mode != CPUExecutionMode::Interpreter &&
180
!m_dialog->hasGameTrait(GameDatabase::Trait::ForceRecompilerICache));
181
}
182
183
void ConsoleSettingsWidget::onEnableCPUClockSpeedControlChecked(int state)
184
{
185
if (state == Qt::Checked &&
186
(!m_dialog->isPerGameSettings() || !Host::GetBaseBoolSettingValue("CPU", "OverclockEnable", false)) &&
187
!Host::GetBaseBoolSettingValue("UI", "CPUOverclockingWarningShown", false))
188
{
189
const QString message =
190
tr("Enabling CPU overclocking will break games, cause bugs, reduce performance and can significantly increase "
191
"system requirements.\n\nBy enabling this option you are agreeing to not create any bug reports unless you "
192
"have confirmed the bug also occurs with overclocking disabled.\n\nThis warning will only be shown once.");
193
194
QMessageBox* mb =
195
new QMessageBox(QMessageBox::Warning, tr("CPU Overclocking Warning"), message, QMessageBox::NoButton, this);
196
mb->setAttribute(Qt::WA_DeleteOnClose, true);
197
mb->setWindowModality(Qt::WindowModal);
198
const QPushButton* const yes_button =
199
mb->addButton(tr("Yes, I will confirm bugs without overclocking before reporting."), QMessageBox::YesRole);
200
const QPushButton* const no_button = mb->addButton(tr("No, take me back to safety."), QMessageBox::NoRole);
201
connect(no_button, &QPushButton::clicked, this, [this]() {
202
QSignalBlocker sb(m_ui.enableCPUClockSpeedControl);
203
if (m_dialog->isPerGameSettings())
204
{
205
m_ui.enableCPUClockSpeedControl->setCheckState(Qt::PartiallyChecked);
206
m_dialog->removeSettingValue("CPU", "OverclockEnable");
207
}
208
else
209
{
210
m_ui.enableCPUClockSpeedControl->setCheckState(Qt::Unchecked);
211
m_dialog->setBoolSettingValue("CPU", "OverclockEnable", false);
212
}
213
214
m_ui.cpuClockSpeed->setEnabled(m_dialog->getEffectiveBoolValue("CPU", "OverclockEnable", false));
215
updateCPUClockSpeedLabel();
216
});
217
connect(yes_button, &QPushButton::clicked, this, []() {
218
Host::SetBaseBoolSettingValue("UI", "CPUOverclockingWarningShown", true);
219
Host::CommitBaseSettingChanges();
220
});
221
mb->show();
222
}
223
224
m_ui.cpuClockSpeed->setEnabled(m_dialog->getEffectiveBoolValue("CPU", "OverclockEnable", false));
225
updateCPUClockSpeedLabel();
226
}
227
228
void ConsoleSettingsWidget::onCPUClockSpeedValueChanged(int value)
229
{
230
const u32 percent = static_cast<u32>(m_ui.cpuClockSpeed->value());
231
u32 numerator, denominator;
232
Settings::CPUOverclockPercentToFraction(percent, &numerator, &denominator);
233
m_dialog->setIntSettingValue("CPU", "OverclockNumerator", static_cast<int>(numerator));
234
m_dialog->setIntSettingValue("CPU", "OverclockDenominator", static_cast<int>(denominator));
235
updateCPUClockSpeedLabel();
236
}
237
238
void ConsoleSettingsWidget::updateCPUClockSpeedLabel()
239
{
240
const int percent = m_ui.enableCPUClockSpeedControl->isChecked() ? m_ui.cpuClockSpeed->value() : 100;
241
const double frequency = (static_cast<double>(System::MASTER_CLOCK) * static_cast<double>(percent)) / 100.0;
242
m_ui.cpuClockSpeedLabel->setText(tr("%1% (%2MHz)").arg(percent).arg(frequency / 1000000.0, 0, 'f', 2));
243
}
244
245
void ConsoleSettingsWidget::calculateCPUClockValue()
246
{
247
const u32 numerator = static_cast<u32>(m_dialog->getEffectiveIntValue("CPU", "OverclockNumerator", 1));
248
const u32 denominator = static_cast<u32>(m_dialog->getEffectiveIntValue("CPU", "OverclockDenominator", 1));
249
const u32 percent = Settings::CPUOverclockFractionToPercent(numerator, denominator);
250
QSignalBlocker sb(m_ui.cpuClockSpeed);
251
m_ui.cpuClockSpeed->setValue(static_cast<int>(percent));
252
updateCPUClockSpeedLabel();
253
}
254
255