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
This commit is contained in:
James Rowe 2019-12-15 22:04:33 -07:00
parent 55ec7031cc
commit 276d56ca9b
15 changed files with 204 additions and 84 deletions

View file

@ -104,6 +104,8 @@ void Config::ReadValues() {
// Core // Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); 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 // Renderer
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false); Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false);

View file

@ -91,6 +91,12 @@ udp_pad_index=
# 0: Interpreter (slow), 1 (default): JIT (fast) # 0: Interpreter (slow), 1 (default): JIT (fast)
use_cpu_jit = 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] [Renderer]
# Whether to render using GLES or OpenGL # Whether to render using GLES or OpenGL
# 0 (default): OpenGL, 1: GLES # 0 (default): OpenGL, 1: GLES

View file

@ -253,6 +253,8 @@ void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core")); qt_config->beginGroup(QStringLiteral("Core"));
Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool(); 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(); qt_config->endGroup();
} }
@ -730,6 +732,8 @@ void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core")); qt_config->beginGroup(QStringLiteral("Core"));
WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true); 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(); qt_config->endGroup();
} }

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>345</width> <width>345</width>
<height>357</height> <height>358</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -68,6 +68,13 @@
<string>Emulation</string> <string>Emulation</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QCheckBox" name="toggle_frame_limit">
<property name="text">
<string>Limit Speed Percent</string>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -119,13 +126,6 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QCheckBox" name="toggle_frame_limit">
<property name="text">
<string>Limit Speed Percent</string>
</property>
</widget>
</item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="frame_limit"> <widget class="QSpinBox" name="frame_limit">
<property name="suffix"> <property name="suffix">

View file

@ -217,6 +217,17 @@ static const std::array<const char*, 187> country_names = {
QT_TRANSLATE_NOOP("ConfigureSystem", "Bermuda"), // 180-186 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) { ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
ui->setupUi(this); ui->setupUi(this);
connect(ui->combo_birthmonth, 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(); ConfigureTime();
} }
@ -258,6 +273,10 @@ void ConfigureSystem::SetConfiguration() {
ui->label_disable_info->hide(); 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() { void ConfigureSystem::ReadSystemSettings() {
@ -299,10 +318,7 @@ void ConfigureSystem::ReadSystemSettings() {
} }
void ConfigureSystem::ApplyConfiguration() { void ConfigureSystem::ApplyConfiguration() {
if (!enabled) { if (enabled) {
return;
}
bool modified = false; bool modified = false;
// apply username // apply username
@ -358,6 +374,9 @@ void ConfigureSystem::ApplyConfiguration() {
Settings::values.init_clock = Settings::values.init_clock =
static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex()); static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex());
Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t(); Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();
}
Settings::values.cpu_clock_percentage = SliderToSettings(ui->slider_clock_speed->value());
Settings::Apply(); Settings::Apply();
} }

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>360</width> <width>471</width>
<height>377</height> <height>555</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -228,8 +228,7 @@
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="4" column="1">
<widget class="QComboBox" name="combo_country"> <widget class="QComboBox" name="combo_country"/>
</widget>
</item> </item>
<item row="5" column="0"> <item row="5" column="0">
<widget class="QLabel" name="label_init_clock"> <widget class="QLabel" name="label_init_clock">
@ -306,6 +305,63 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Advanced</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>CPU Clock Speed</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSlider" name="slider_clock_speed">
<property name="toolTip">
<string>&lt;html&gt;&lt;body&gt;Changes the emulated CPU clock frequency.&lt;br&gt;Underclocking can increase performance but may cause the game to freeze.&lt;br&gt;Overclocking may reduce in game lag but also might cause freezes&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>79</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="pageStep">
<number>15</number>
</property>
<property name="value">
<number>25</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="clock_display_label">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2"/>
</item>
<item> <item>
<widget class="QLabel" name="label_disable_info"> <widget class="QLabel" name="label_disable_info">
<property name="text"> <property name="text">
@ -316,6 +372,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="label_cpu_clock_info">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;CPU Clock Speed Information&lt;br/&gt;Underclocking can increase performance but may cause the game to freeze.&lt;br/&gt;Overclocking may reduce in game lag but also might cause freezes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View file

@ -256,7 +256,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
memory = std::make_unique<Memory::MemorySystem>(); memory = std::make_unique<Memory::MemorySystem>();
timing = std::make_unique<Timing>(num_cores); timing = std::make_unique<Timing>(num_cores, Settings::values.cpu_clock_percentage);
kernel = std::make_unique<Kernel::KernelSystem>( kernel = std::make_unique<Kernel::KernelSystem>(
*memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores); *memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores);

View file

@ -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); 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); timers.resize(num_cores);
for (std::size_t i = 0; i < num_cores; ++i) { for (std::size_t i = 0; i < num_cores; ++i) {
timers[i] = std::make_shared<Timer>(); timers[i] = std::make_shared<Timer>(100.0 / cpu_clock_percentage);
} }
current_timer = timers[0]; 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) { TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback callback) {
// check for existing type with same name. // check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization. // we want event type names to remain unique so that we can use them for serialization.
@ -117,6 +123,8 @@ std::shared_ptr<Timing::Timer> Timing::GetTimer(std::size_t cpu_id) {
return timers[cpu_id]; return timers[cpu_id];
} }
Timing::Timer::Timer(double cpu_clock_scale_) : cpu_clock_scale(cpu_clock_scale_) {}
Timing::Timer::~Timer() { Timing::Timer::~Timer() {
MoveEvents(); MoveEvents();
} }
@ -130,7 +138,7 @@ u64 Timing::Timer::GetTicks() const {
} }
void Timing::Timer::AddTicks(u64 ticks) { void Timing::Timer::AddTicks(u64 ticks) {
downcount -= ticks; downcount -= static_cast<u64>(ticks * cpu_clock_scale);
} }
u64 Timing::Timer::GetIdleTicks() const { u64 Timing::Timer::GetIdleTicks() const {

View file

@ -148,6 +148,7 @@ public:
class Timer { class Timer {
public: public:
Timer(double cpu_clock_scale);
~Timer(); ~Timer();
s64 GetMaxSliceLength() const; s64 GetMaxSliceLength() const;
@ -190,10 +191,13 @@ public:
s64 slice_length = MAX_SLICE_LENGTH; s64 slice_length = MAX_SLICE_LENGTH;
s64 downcount = MAX_SLICE_LENGTH; s64 downcount = MAX_SLICE_LENGTH;
s64 executed_ticks = 0; 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(){}; ~Timing(){};
@ -220,6 +224,11 @@ public:
global_timer += ticks; 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::chrono::microseconds GetGlobalTimeUs() const;
std::shared_ptr<Timer> GetTimer(std::size_t cpu_id); std::shared_ptr<Timer> 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 // unordered_map stores each element separately as a linked list node so pointers to
// elements remain stable regardless of rehashes/resizing. // elements remain stable regardless of rehashes/resizing.
std::unordered_map<std::string, TimingEventType> event_types; std::unordered_map<std::string, TimingEventType> event_types = {};
std::vector<std::shared_ptr<Timer>> timers; std::vector<std::shared_ptr<Timer>> timers;
std::shared_ptr<Timer> current_timer; std::shared_ptr<Timer> 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 } // namespace Core

View file

@ -40,6 +40,7 @@ void Apply() {
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) { if (system.IsPoweredOn()) {
system.CoreTiming().UpdateClockSpeed(values.cpu_clock_percentage);
Core::DSP().SetSink(values.sink_id, values.audio_device_id); Core::DSP().SetSink(values.sink_id, values.audio_device_id);
Core::DSP().EnableStretching(values.enable_audio_stretching); Core::DSP().EnableStretching(values.enable_audio_stretching);

View file

@ -128,6 +128,7 @@ struct Values {
// Core // Core
bool use_cpu_jit; bool use_cpu_jit;
int cpu_clock_percentage;
// Data Storage // Data Storage
bool use_virtual_sd; bool use_virtual_sd;

View file

@ -15,7 +15,7 @@ static Memory::PageTable* page_table = nullptr;
TestEnvironment::TestEnvironment(bool mutable_memory_) TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
timing = std::make_unique<Core::Timing>(1); timing = std::make_unique<Core::Timing>(1, 100);
memory = std::make_unique<Memory::MemorySystem>(); memory = std::make_unique<Memory::MemorySystem>();
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, [] {}, 0, 1); kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, [] {}, 0, 1);

View file

@ -43,7 +43,7 @@ static void AdvanceAndCheck(Core::Timing& timing, u32 idx, int downcount, int ex
} }
TEST_CASE("CoreTiming[BasicOrder]", "[core]") { 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_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>); 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]") { TEST_CASE("CoreTiming[SharedSlot]", "[core]") {
using namespace SharedSlotTest; using namespace SharedSlotTest;
Core::Timing timing(1); Core::Timing timing(1, 100);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", FifoCallback<0>); Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", FifoCallback<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", FifoCallback<1>); Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", FifoCallback<1>);
@ -118,7 +118,7 @@ TEST_CASE("CoreTiming[SharedSlot]", "[core]") {
} }
TEST_CASE("CoreTiming[PredictableLateness]", "[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_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>); 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]") { TEST_CASE("CoreTiming[ChainScheduling]", "[core]") {
using namespace ChainSchedulingTest; using namespace ChainSchedulingTest;
Core::Timing timing(1); Core::Timing timing(1, 100);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>); Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>); Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);

View file

@ -21,7 +21,7 @@ static std::shared_ptr<Object> MakeObject(Kernel::KernelSystem& kernel) {
} }
TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") { TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") {
Core::Timing timing(1); Core::Timing timing(1, 100);
Memory::MemorySystem memory; Memory::MemorySystem memory;
Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1); Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1);
auto [server, client] = kernel.CreateSessionPair(); auto [server, client] = kernel.CreateSessionPair();
@ -233,7 +233,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
} }
TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
Core::Timing timing(1); Core::Timing timing(1, 100);
Memory::MemorySystem memory; Memory::MemorySystem memory;
Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1); Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1);
auto [server, client] = kernel.CreateSessionPair(); auto [server, client] = kernel.CreateSessionPair();

View file

@ -11,7 +11,7 @@
#include "core/memory.h" #include "core/memory.h"
TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") { TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") {
Core::Timing timing(1); Core::Timing timing(1, 100);
Memory::MemorySystem memory; Memory::MemorySystem memory;
Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1); Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1);
SECTION("these regions should not be mapped on an empty process") { SECTION("these regions should not be mapped on an empty process") {