diff --git a/src/citra/config.cpp b/src/citra/config.cpp index e47820e18..47270ab36 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 c3ffb37d3..881700913 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 1c0de30ba..d139a89e9 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(); } @@ -737,6 +739,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 69626d4e4..65b7e1cd3 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -258,7 +258,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, n3ds_mode); 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 f6792604b..13b1e965f 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -44,6 +44,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 bb04d9d9f..aa7e781b5 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 583459e7c..a6fd0de76 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, 0); 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 d1aacd739..cb73d481f 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 MakeObject(Kernel::KernelSystem& kernel) { } TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") { - Core::Timing timing(1); + Core::Timing timing(1, 100); Memory::MemorySystem memory; Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1, 0); auto [server, client] = kernel.CreateSessionPair(); @@ -233,7 +233,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel } TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { - Core::Timing timing(1); + Core::Timing timing(1, 100); Memory::MemorySystem memory; Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1, 0); auto [server, client] = kernel.CreateSessionPair(); diff --git a/src/tests/core/memory/memory.cpp b/src/tests/core/memory/memory.cpp index 8f08862a1..6a499ed62 100644 --- a/src/tests/core/memory/memory.cpp +++ b/src/tests/core/memory/memory.cpp @@ -11,7 +11,7 @@ #include "core/memory.h" TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") { - Core::Timing timing(1); + Core::Timing timing(1, 100); Memory::MemorySystem memory; Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1, 0); SECTION("these regions should not be mapped on an empty process") {