diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index b2c878ddf..daaacd6d4 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -104,6 +104,8 @@ void Config::ReadValues() {
// Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
+ Settings::values.cpu_clock_percentage =
+ sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100);
// Renderer
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false);
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 9c441e354..0a0be12f3 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -91,6 +91,12 @@ udp_pad_index=
# 0: Interpreter (slow), 1 (default): JIT (fast)
use_cpu_jit =
+# Change the Clock Frequency of the emulated 3DS CPU.
+# Underclocking can increase the performance of the game at the risk of freezing.
+# Overclocking may fix lag that happens on console, but also comes with the risk of freezing.
+# Range is any positive integer (but we suspect 25 - 400 is a good idea) Default is 100
+cpu_clock_percentage =
+
[Renderer]
# Whether to render using GLES or OpenGL
# 0 (default): OpenGL, 1: GLES
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 82274bff0..368be2b31 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -253,6 +253,8 @@ void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool();
+ Settings::values.cpu_clock_percentage =
+ ReadSetting(QStringLiteral("cpu_clock_percentage"), 100).toInt();
qt_config->endGroup();
}
@@ -730,6 +732,8 @@ void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true);
+ WriteSetting(QStringLiteral("cpu_clock_percentage"), Settings::values.cpu_clock_percentage,
+ 100);
qt_config->endGroup();
}
diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui
index 2a461a05d..181455a64 100644
--- a/src/citra_qt/configuration/configure_general.ui
+++ b/src/citra_qt/configuration/configure_general.ui
@@ -7,7 +7,7 @@
0
0
345
- 357
+ 358
@@ -68,6 +68,13 @@
Emulation
+ -
+
+
+ Limit Speed Percent
+
+
+
-
@@ -119,13 +126,6 @@
- -
-
-
- Limit Speed Percent
-
-
-
-
diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp
index 775bc1eeb..139d92f7b 100644
--- a/src/citra_qt/configuration/configure_system.cpp
+++ b/src/citra_qt/configuration/configure_system.cpp
@@ -217,6 +217,17 @@ static const std::array country_names = {
QT_TRANSLATE_NOOP("ConfigureSystem", "Bermuda"), // 180-186
};
+// The QSlider doesn't have an easy way to set a custom step amount,
+// so we can just convert from the sliders range (0 - 79) to the expected
+// settings range (5 - 400) with simple math.
+static constexpr int SliderToSettings(int value) {
+ return 5 * value + 5;
+}
+
+static constexpr int SettingsToSlider(int value) {
+ return (value - 5) / 5;
+}
+
ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
ui->setupUi(this);
connect(ui->combo_birthmonth,
@@ -233,6 +244,10 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::
}
}
+ connect(ui->slider_clock_speed, &QSlider::valueChanged, [&](int value) {
+ ui->clock_display_label->setText(QStringLiteral("%1%").arg(SliderToSettings(value)));
+ });
+
ConfigureTime();
}
@@ -258,6 +273,10 @@ void ConfigureSystem::SetConfiguration() {
ui->label_disable_info->hide();
}
+
+ ui->slider_clock_speed->setValue(SettingsToSlider(Settings::values.cpu_clock_percentage));
+ ui->clock_display_label->setText(
+ QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage));
}
void ConfigureSystem::ReadSystemSettings() {
@@ -299,65 +318,65 @@ void ConfigureSystem::ReadSystemSettings() {
}
void ConfigureSystem::ApplyConfiguration() {
- if (!enabled) {
- return;
+ if (enabled) {
+ bool modified = false;
+
+ // apply username
+ // TODO(wwylele): Use this when we move to Qt 5.5
+ // std::u16string new_username = ui->edit_username->text().toStdU16String();
+ std::u16string new_username(
+ reinterpret_cast(ui->edit_username->text().utf16()));
+ if (new_username != username) {
+ cfg->SetUsername(new_username);
+ modified = true;
+ }
+
+ // apply birthday
+ int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
+ int new_birthday = ui->combo_birthday->currentIndex() + 1;
+ if (birthmonth != new_birthmonth || birthday != new_birthday) {
+ cfg->SetBirthday(new_birthmonth, new_birthday);
+ modified = true;
+ }
+
+ // apply language
+ int new_language = ui->combo_language->currentIndex();
+ if (language_index != new_language) {
+ cfg->SetSystemLanguage(static_cast(new_language));
+ modified = true;
+ }
+
+ // apply sound
+ int new_sound = ui->combo_sound->currentIndex();
+ if (sound_index != new_sound) {
+ cfg->SetSoundOutputMode(static_cast(new_sound));
+ modified = true;
+ }
+
+ // apply country
+ u8 new_country = static_cast(ui->combo_country->currentData().toInt());
+ if (country_code != new_country) {
+ cfg->SetCountryCode(new_country);
+ modified = true;
+ }
+
+ // apply play coin
+ u16 new_play_coin = static_cast(ui->spinBox_play_coins->value());
+ if (play_coin != new_play_coin) {
+ Service::PTM::Module::SetPlayCoins(new_play_coin);
+ }
+
+ // update the config savegame if any item is modified.
+ if (modified) {
+ cfg->UpdateConfigNANDSavegame();
+ }
+
+ Settings::values.init_clock =
+ static_cast(ui->combo_init_clock->currentIndex());
+ Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();
}
- bool modified = false;
-
- // apply username
- // TODO(wwylele): Use this when we move to Qt 5.5
- // std::u16string new_username = ui->edit_username->text().toStdU16String();
- std::u16string new_username(
- reinterpret_cast(ui->edit_username->text().utf16()));
- if (new_username != username) {
- cfg->SetUsername(new_username);
- modified = true;
- }
-
- // apply birthday
- int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
- int new_birthday = ui->combo_birthday->currentIndex() + 1;
- if (birthmonth != new_birthmonth || birthday != new_birthday) {
- cfg->SetBirthday(new_birthmonth, new_birthday);
- modified = true;
- }
-
- // apply language
- int new_language = ui->combo_language->currentIndex();
- if (language_index != new_language) {
- cfg->SetSystemLanguage(static_cast(new_language));
- modified = true;
- }
-
- // apply sound
- int new_sound = ui->combo_sound->currentIndex();
- if (sound_index != new_sound) {
- cfg->SetSoundOutputMode(static_cast(new_sound));
- modified = true;
- }
-
- // apply country
- u8 new_country = static_cast(ui->combo_country->currentData().toInt());
- if (country_code != new_country) {
- cfg->SetCountryCode(new_country);
- modified = true;
- }
-
- // apply play coin
- u16 new_play_coin = static_cast(ui->spinBox_play_coins->value());
- if (play_coin != new_play_coin) {
- Service::PTM::Module::SetPlayCoins(new_play_coin);
- }
-
- // update the config savegame if any item is modified.
- if (modified) {
- cfg->UpdateConfigNANDSavegame();
- }
-
- Settings::values.init_clock =
- static_cast(ui->combo_init_clock->currentIndex());
- Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();
+ Settings::values.cpu_clock_percentage = SliderToSettings(ui->slider_clock_speed->value());
Settings::Apply();
}
diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui
index 51ad7c8ca..554993990 100644
--- a/src/citra_qt/configuration/configure_system.ui
+++ b/src/citra_qt/configuration/configure_system.ui
@@ -6,8 +6,8 @@
0
0
- 360
- 377
+ 471
+ 555
@@ -228,8 +228,7 @@
-
-
-
+
-
@@ -306,6 +305,63 @@
+ -
+
+
+ Advanced
+
+
+
-
+
+
+ CPU Clock Speed
+
+
+
+ -
+
+
+ <html><body>Changes the emulated CPU clock frequency.<br>Underclocking can increase performance but may cause the game to freeze.<br>Overclocking may reduce in game lag but also might cause freezes</body></html>
+
+
+ 0
+
+
+ 79
+
+
+ 5
+
+
+ 15
+
+
+ 25
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBelow
+
+
+
+ -
+
+
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+
+ -
+
+
-
@@ -316,6 +372,16 @@
+ -
+
+
+ <html><head/><body><p>CPU Clock Speed Information<br/>Underclocking can increase performance but may cause the game to freeze.<br/>Overclocking may reduce in game lag but also might cause freezes</p></body></html>
+
+
+ Qt::RichText
+
+
+
-
diff --git a/src/core/core.cpp b/src/core/core.cpp
index cd1799e42..7418adf83 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -256,7 +256,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
memory = std::make_unique();
- timing = std::make_unique(num_cores);
+ timing = std::make_unique(num_cores, Settings::values.cpu_clock_percentage);
kernel = std::make_unique(
*memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores);
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 8966bc55b..5dbd5f74c 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -20,14 +20,20 @@ bool Timing::Event::operator<(const Timing::Event& right) const {
return std::tie(time, fifo_order) < std::tie(right.time, right.fifo_order);
}
-Timing::Timing(std::size_t num_cores) {
+Timing::Timing(std::size_t num_cores, u32 cpu_clock_percentage) {
timers.resize(num_cores);
for (std::size_t i = 0; i < num_cores; ++i) {
- timers[i] = std::make_shared();
+ timers[i] = std::make_shared(100.0 / cpu_clock_percentage);
}
current_timer = timers[0];
}
+void Timing::UpdateClockSpeed(u32 cpu_clock_percentage) {
+ for (auto& timer : timers) {
+ timer->cpu_clock_scale = 100.0 / cpu_clock_percentage;
+ }
+}
+
TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback callback) {
// check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization.
@@ -117,6 +123,8 @@ std::shared_ptr Timing::GetTimer(std::size_t cpu_id) {
return timers[cpu_id];
}
+Timing::Timer::Timer(double cpu_clock_scale_) : cpu_clock_scale(cpu_clock_scale_) {}
+
Timing::Timer::~Timer() {
MoveEvents();
}
@@ -130,7 +138,7 @@ u64 Timing::Timer::GetTicks() const {
}
void Timing::Timer::AddTicks(u64 ticks) {
- downcount -= ticks;
+ downcount -= static_cast(ticks * cpu_clock_scale);
}
u64 Timing::Timer::GetIdleTicks() const {
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 30c1106bb..929f39865 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -148,6 +148,7 @@ public:
class Timer {
public:
+ Timer(double cpu_clock_scale);
~Timer();
s64 GetMaxSliceLength() const;
@@ -190,10 +191,13 @@ public:
s64 slice_length = MAX_SLICE_LENGTH;
s64 downcount = MAX_SLICE_LENGTH;
s64 executed_ticks = 0;
- u64 idled_cycles;
+ u64 idled_cycles = 0;
+ // Stores a scaling for the internal clockspeed. Changing this number results in
+ // under/overclocking the guest cpu
+ double cpu_clock_scale = 1.0;
};
- explicit Timing(std::size_t num_cores);
+ explicit Timing(std::size_t num_cores, u32 cpu_clock_percentage);
~Timing(){};
@@ -220,6 +224,11 @@ public:
global_timer += ticks;
}
+ /**
+ * Updates the value of the cpu clock scaling to the new percentage.
+ */
+ void UpdateClockSpeed(u32 cpu_clock_percentage);
+
std::chrono::microseconds GetGlobalTimeUs() const;
std::shared_ptr GetTimer(std::size_t cpu_id);
@@ -229,10 +238,14 @@ private:
// unordered_map stores each element separately as a linked list node so pointers to
// elements remain stable regardless of rehashes/resizing.
- std::unordered_map event_types;
+ std::unordered_map event_types = {};
std::vector> timers;
std::shared_ptr current_timer;
+
+ // Stores a scaling for the internal clockspeed. Changing this number results in
+ // under/overclocking the guest cpu
+ double cpu_clock_scale = 1.0;
};
} // namespace Core
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index ec3a36115..90bf101de 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -40,6 +40,7 @@ void Apply() {
auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) {
+ system.CoreTiming().UpdateClockSpeed(values.cpu_clock_percentage);
Core::DSP().SetSink(values.sink_id, values.audio_device_id);
Core::DSP().EnableStretching(values.enable_audio_stretching);
diff --git a/src/core/settings.h b/src/core/settings.h
index 78b11912c..83ce19223 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -128,6 +128,7 @@ struct Values {
// Core
bool use_cpu_jit;
+ int cpu_clock_percentage;
// Data Storage
bool use_virtual_sd;
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 0957c7c97..44e856405 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -15,7 +15,7 @@ static Memory::PageTable* page_table = nullptr;
TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_), test_memory(std::make_shared(this)) {
- timing = std::make_unique(1);
+ timing = std::make_unique(1, 100);
memory = std::make_unique();
kernel = std::make_unique(*memory, *timing, [] {}, 0, 1);
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp
index 850f13bc5..8b34ba9d7 100644
--- a/src/tests/core/core_timing.cpp
+++ b/src/tests/core/core_timing.cpp
@@ -43,7 +43,7 @@ static void AdvanceAndCheck(Core::Timing& timing, u32 idx, int downcount, int ex
}
TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
- Core::Timing timing(1);
+ Core::Timing timing(1, 100);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
@@ -90,7 +90,7 @@ void FifoCallback(u64 userdata, s64 cycles_late) {
TEST_CASE("CoreTiming[SharedSlot]", "[core]") {
using namespace SharedSlotTest;
- Core::Timing timing(1);
+ Core::Timing timing(1, 100);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", FifoCallback<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", FifoCallback<1>);
@@ -118,7 +118,7 @@ TEST_CASE("CoreTiming[SharedSlot]", "[core]") {
}
TEST_CASE("CoreTiming[PredictableLateness]", "[core]") {
- Core::Timing timing(1);
+ Core::Timing timing(1, 100);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
@@ -149,7 +149,7 @@ static void RescheduleCallback(Core::Timing& timing, u64 userdata, s64 cycles_la
TEST_CASE("CoreTiming[ChainScheduling]", "[core]") {
using namespace ChainSchedulingTest;
- Core::Timing timing(1);
+ Core::Timing timing(1, 100);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
diff --git a/src/tests/core/hle/kernel/hle_ipc.cpp b/src/tests/core/hle/kernel/hle_ipc.cpp
index 59026afd6..52ff3b117 100644
--- a/src/tests/core/hle/kernel/hle_ipc.cpp
+++ b/src/tests/core/hle/kernel/hle_ipc.cpp
@@ -21,7 +21,7 @@ static std::shared_ptr