From 276d56ca9b2f69cdc71f2d80653922e2cca253ff Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sun, 15 Dec 2019 22:04:33 -0700 Subject: [PATCH] Add CPU Clock Frequency slider This slider affects the number of cycles that the guest cpu emulation reports that have passed since the last time slice. This option scales the result returned by a percentage that the user selects. In some games underclocking the CPU can give a major speedup. Exposing this as an option will give users something to toy with for performance, while also potentially enhancing games that experience lag on the real console --- src/citra/config.cpp | 2 + src/citra/default_ini.h | 6 + src/citra_qt/configuration/config.cpp | 4 + .../configuration/configure_general.ui | 16 +-- .../configuration/configure_system.cpp | 133 ++++++++++-------- .../configuration/configure_system.ui | 74 +++++++++- src/core/core.cpp | 2 +- src/core/core_timing.cpp | 14 +- src/core/core_timing.h | 19 ++- src/core/settings.cpp | 1 + src/core/settings.h | 1 + src/tests/core/arm/arm_test_common.cpp | 2 +- src/tests/core/core_timing.cpp | 8 +- src/tests/core/hle/kernel/hle_ipc.cpp | 4 +- src/tests/core/memory/memory.cpp | 2 +- 15 files changed, 204 insertions(+), 84 deletions(-) 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 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); 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); auto [server, client] = kernel.CreateSessionPair(); diff --git a/src/tests/core/memory/memory.cpp b/src/tests/core/memory/memory.cpp index 2e7c71434..b3eed7a9c 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); SECTION("these regions should not be mapped on an empty process") {