From 276d56ca9b2f69cdc71f2d80653922e2cca253ff Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sun, 15 Dec 2019 22:04:33 -0700 Subject: [PATCH 1/2] 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 b2c878ddf6..daaacd6d4f 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 9c441e3542..0a0be12f35 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 82274bff09..368be2b315 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 2a461a05db..181455a646 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 775bc1eeb3..139d92f7ba 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 51ad7c8cab..5549939902 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 cd1799e425..7418adf834 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 8966bc55bc..5dbd5f74cd 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 30c1106bbe..929f398656 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 ec3a361158..90bf101def 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 78b11912c5..83ce192231 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 0957c7c975..44e8564053 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 850f13bc5c..8b34ba9d79 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 59026afd64..52ff3b1170 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 2e7c714347..b3eed7a9c1 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") { From 3edc4a3055a6e056f2ebc02c8f57262dd9f762b7 Mon Sep 17 00:00:00 2001 From: Pengfei Zhu Date: Sat, 28 Mar 2020 20:26:54 +0800 Subject: [PATCH 2/2] service/ldr_ro: Fix CRO loading when the buffer contained multiple VM areas (#5125) * vm_manager: Handle multiple areas in ChangeMemoryState It is possible that a few areas have the same permisson and state, but with different backing pointers. Currently, this function assumes that only one continous area is found, but this is not always the case. * service/ldr_ro: Handle multiple areas in VerifyBufferState It is possible that the buffer passed from the game is made up of multiple areas with the same permisson and state but different backing pointers. Change the check to allow that. --- src/core/hle/kernel/vm_manager.cpp | 15 +++++++++------ src/core/hle/service/ldr_ro/ldr_ro.cpp | 15 +++++++++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 3280f99e98..ba2e2bd1a0 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -151,13 +151,16 @@ ResultCode VMManager::ChangeMemoryState(VAddr target, u32 size, MemoryState expe } CASCADE_RESULT(auto vma, CarveVMARange(target, size)); - ASSERT(vma->second.size == size); - vma->second.permissions = new_perms; - vma->second.meminfo_state = new_state; - UpdatePageTableForVMA(vma->second); - - MergeAdjacent(vma); + const VMAIter end = vma_map.end(); + // The comparison against the end of the range must be done using addresses since VMAs can be + // merged during this process, causing invalidation of the iterators. + while (vma != end && vma->second.base < target_end) { + vma->second.permissions = new_perms; + vma->second.meminfo_state = new_state; + UpdatePageTableForVMA(vma->second); + vma = std::next(MergeAdjacent(vma)); + } return RESULT_SUCCESS; } diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp index 274d36ed5d..a435d5e58f 100644 --- a/src/core/hle/service/ldr_ro/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -41,10 +41,17 @@ static const ResultCode ERROR_NOT_LOADED = // 0xD8A12C0D static bool VerifyBufferState(Kernel::Process& process, VAddr buffer_ptr, u32 size) { auto vma = process.vm_manager.FindVMA(buffer_ptr); - return vma != process.vm_manager.vma_map.end() && - vma->second.base + vma->second.size >= buffer_ptr + size && - vma->second.permissions == Kernel::VMAPermission::ReadWrite && - vma->second.meminfo_state == Kernel::MemoryState::Private; + while (vma != process.vm_manager.vma_map.end()) { + if (vma->second.permissions != Kernel::VMAPermission::ReadWrite || + vma->second.meminfo_state != Kernel::MemoryState::Private) { + return false; + } + if (vma->second.base + vma->second.size >= buffer_ptr + size) { + return true; + } + vma = std::next(vma); + } + return false; } void RO::Initialize(Kernel::HLERequestContext& ctx) {