feat: add CPU clock rate slider to settings

Implement a slider in the CPU settings tab to adjust the BASE_CLOCK_RATE
up to 1,785 MHz (Switch's official maximum clock rate). Default remains
at 1,020 MHz.

This change:
- Adds UI slider and spinbox to configure_cpu.ui with range 500-1785 MHz
- Makes BASE_CLOCK_RATE dynamic by reading from settings
- Modifies WallClock to handle dynamic clock rate changes
- Updates APM controller to properly set the clock rate
- Changes clock rate settings category from Core to CPU

The user can now easily adjust the CPU clock rate to improve performance
or manage thermals and power consumption.

Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron 2025-04-16 22:02:10 +10:00
parent bbd3253169
commit 278486d059
6 changed files with 116 additions and 10 deletions

View File

@ -1,10 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <memory> #include <memory>
#include <typeinfo> #include <typeinfo>
#include <vector> #include <vector>
#include <QComboBox> #include <QComboBox>
#include <QSpinBox>
#include <QSlider>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/settings_enums.h" #include "common/settings_enums.h"
@ -38,7 +41,22 @@ ConfigureCpu::ConfigureCpu(const Core::System& system_,
ConfigureCpu::~ConfigureCpu() = default; ConfigureCpu::~ConfigureCpu() = default;
void ConfigureCpu::SetConfiguration() {} void ConfigureCpu::SetConfiguration() {
// Set clock rate values from settings
const u32 clock_rate_mhz = Settings::values.cpu_clock_rate.GetValue() / 1'000'000;
ui->clock_rate_slider->setValue(static_cast<int>(clock_rate_mhz));
ui->clock_rate_spinbox->setValue(static_cast<int>(clock_rate_mhz));
// Connect slider and spinbox signals to keep them in sync
connect(ui->clock_rate_slider, &QSlider::valueChanged, this, [this](int value) {
ui->clock_rate_spinbox->setValue(value);
});
connect(ui->clock_rate_spinbox, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int value) {
ui->clock_rate_slider->setValue(value);
});
}
void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
auto* accuracy_layout = ui->widget_accuracy->layout(); auto* accuracy_layout = ui->widget_accuracy->layout();
auto* backend_layout = ui->widget_backend->layout(); auto* backend_layout = ui->widget_backend->layout();
@ -99,6 +117,9 @@ void ConfigureCpu::ApplyConfiguration() {
for (const auto& apply_func : apply_funcs) { for (const auto& apply_func : apply_funcs) {
apply_func(is_powered_on); apply_func(is_powered_on);
} }
// Save the clock rate setting (convert from MHz to Hz)
Settings::values.cpu_clock_rate = static_cast<u32>(ui->clock_rate_spinbox->value()) * 1'000'000;
} }
void ConfigureCpu::changeEvent(QEvent* event) { void ConfigureCpu::changeEvent(QEvent* event) {

View File

@ -126,6 +126,67 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="clock_rate_group">
<property name="title">
<string>CPU Clock Rate</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="label_clock_description">
<property name="text">
<string>CPU clock rate in MHz. Setting a higher clock rate will improve performance but may cause system instability. Default is 1020 MHz.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QSlider" name="clock_rate_slider">
<property name="minimum">
<number>500</number>
</property>
<property name="maximum">
<number>1785</number>
</property>
<property name="value">
<number>1020</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="clock_rate_spinbox">
<property name="suffix">
<string> MHz</string>
</property>
<property name="minimum">
<number>500</number>
</property>
<property name="maximum">
<number>1785</number>
</property>
<property name="value">
<number>1020</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -199,6 +199,7 @@ struct Values {
MemoryLayout::Memory_12Gb, MemoryLayout::Memory_12Gb,
"memory_layout_mode", "memory_layout_mode",
Category::Core}; Category::Core};
SwitchableSetting<u32> cpu_clock_rate{linkage, 1'020'000'000, "cpu_clock_rate", Category::Cpu};
SwitchableSetting<bool> use_speed_limit{ SwitchableSetting<bool> use_speed_limit{
linkage, true, "use_speed_limit", Category::Core, Specialization::Paired, false, true}; linkage, true, "use_speed_limit", Category::Core, Specialization::Paired, false, true};
SwitchableSetting<u16, true> speed_limit{linkage, SwitchableSetting<u16, true> speed_limit{linkage,

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
@ -8,6 +9,7 @@
#include <ratio> #include <ratio>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hardware_properties.h"
namespace Common { namespace Common {
@ -15,7 +17,10 @@ class WallClock {
public: public:
static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz
static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz // Changed from constexpr to function to get dynamic value from settings
static inline u64 CPUTickFreq() {
return Core::Hardware::BASE_CLOCK_RATE();
} // T210/4 A57 CPU Tick Frequency from settings
virtual ~WallClock() = default; virtual ~WallClock() = default;
@ -76,12 +81,28 @@ protected:
using NsToCNTPCTRatio = std::ratio<CNTFRQ, std::nano::den>; using NsToCNTPCTRatio = std::ratio<CNTFRQ, std::nano::den>;
using NsToGPUTickRatio = std::ratio<GPUTickFreq, std::nano::den>; using NsToGPUTickRatio = std::ratio<GPUTickFreq, std::nano::den>;
// Cycle Timing // Cycle Timing - using functions for dynamic values
using CPUTickToNsRatio = std::ratio<std::nano::den, CPUTickFreq>; // Update these to use functions instead of constexpr
using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>; struct CPUTickToNsRatio {
using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>; static inline std::intmax_t num = std::nano::den;
using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>; static inline std::intmax_t den = CPUTickFreq();
};
struct CPUTickToUsRatio {
static inline std::intmax_t num = std::micro::den;
static inline std::intmax_t den = CPUTickFreq();
};
struct CPUTickToCNTPCTRatio {
static inline std::intmax_t num = CNTFRQ;
static inline std::intmax_t den = CPUTickFreq();
};
struct CPUTickToGPUTickRatio {
static inline std::intmax_t num = GPUTickFreq;
static inline std::intmax_t den = CPUTickFreq();
};
}; };
std::unique_ptr<WallClock> CreateOptimalClock(); std::unique_ptr<WallClock> CreateOptimalClock();

View File

@ -9,12 +9,13 @@
#include "common/bit_util.h" #include "common/bit_util.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/settings.h"
namespace Core { namespace Core {
namespace Hardware { namespace Hardware {
constexpr u64 BASE_CLOCK_RATE = 1'020'000'000; // Default CPU Frequency = 1020 MHz inline u64 BASE_CLOCK_RATE() { return Settings::values.cpu_clock_rate.GetValue(); } // Default CPU Frequency set in settings, defaults to 1020 MHz
constexpr u64 CNTFREQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz constexpr u64 CNTFREQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm> #include <algorithm>
@ -81,8 +82,8 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa
void Controller::SetClockSpeed(u32 mhz) { void Controller::SetClockSpeed(u32 mhz) {
LOG_DEBUG(Service_APM, "called, mhz={:08X}", mhz); LOG_DEBUG(Service_APM, "called, mhz={:08X}", mhz);
// TODO(DarkLordZach): Actually signal core_timing to change clock speed. // Update the clock rate setting with the provided MHz value (convert to Hz)
// TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used. Settings::values.cpu_clock_rate = mhz * 1'000'000;
} }
} // namespace Service::APM } // namespace Service::APM