diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 01:55:20 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 02:31:58 -0400 |
| commit | 0218eacfd560d0ce48b3ffe64d279ee85bbf1347 (patch) | |
| tree | cbe764aa642b55cae06e94f15bd0e16f234e6052 /src/common | |
| parent | 08be623a271d13b66497a98265f6832e9aa2c0d0 (diff) | |
nx2: run the guest counter at 31.25 MHz for Switch 2 titles
The Switch 2 counter runs at 31.25 MHz and its SDK converts ticks with
that constant: Mario Kart World's sdk builds 31,250,000 in
nn::os::GetSystemTickFrequency, TickManager::ConvertToTimeSpan and
TickManager::ConvertToTick (19.2 MHz appears nowhere). Eglantine counted
at the Switch 1 rate for every title, so every tick-based time in a
Switch 2 game was off by 1.63x: elapsed times read short, and a deadline
of one 60 Hz frame converted to ticks waited ~27 ms.
The wall clock now has a guest counter frequency, set at boot: 31.25 MHz
for Switch 2 programs, 19.2 MHz otherwise, EDEN_NX2_CNTFRQ=<Hz> to force
one. CNTPCT reads, CNTFRQ_EL0, svcGetSystemTick, thread tick counts and
the time services' tick conversions follow it, and the CNTPCT spin
detection keeps its ~1.7 us threshold.
Mario Kart World, title screen and attract demo, 180 s, same build:
frame times unchanged (17-19 ms once warm), process CPU time 848 s
instead of 930 s.
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/cpu_features.cpp | 31 | ||||
| -rw-r--r-- | src/common/cpu_features.h | 22 |
2 files changed, 45 insertions, 8 deletions
diff --git a/src/common/cpu_features.cpp b/src/common/cpu_features.cpp index 52fd003c44..93eecb1dc5 100644 --- a/src/common/cpu_features.cpp +++ b/src/common/cpu_features.cpp @@ -278,8 +278,16 @@ std::chrono::milliseconds WallClock::GetTimeMS() const { s64 WallClock::GetCNTPCT() const { if (!invariant) - return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; - return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor); + return MultiplyAndDivide64(GetUptime(), GetGuestCNTFRQ(), NsRatio::den); + return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor.load(std::memory_order_relaxed)); +} + +void WallClock::SetGuestCNTFRQ(u64 frequency) { + guest_cntfrq.store(frequency, std::memory_order_relaxed); + if (invariant) { + cntpct_rdtsc_factor.store(GetFixedPoint64Factor(frequency, rdtsc_frequency), + std::memory_order_relaxed); + } } s64 WallClock::GetGPUTick() const { @@ -336,7 +344,7 @@ namespace { } // namespace WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept { - const u64 host_cntfrq = std::max<u64>(GetHostCNTFRQ(), 1); + host_cntfrq = std::max<u64>(GetHostCNTFRQ(), 1); ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq); us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq); ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq); @@ -361,6 +369,11 @@ s64 WallClock::GetCNTPCT() const { return MultiplyHigh(GetUptime(), guest_cntfrq_factor); } +void WallClock::SetGuestCNTFRQ(u64 frequency) { + guest_cntfrq.store(frequency, std::memory_order_relaxed); + guest_cntfrq_factor = GetFixedPointFactor(frequency, host_cntfrq); +} + s64 WallClock::GetGPUTick() const { return MultiplyHigh(GetUptime(), gputick_cntfrq_factor); } @@ -399,7 +412,11 @@ std::chrono::milliseconds WallClock::GetTimeMS() const { } s64 WallClock::GetCNTPCT() const { - return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; + return MultiplyAndDivide64(GetUptime(), GetGuestCNTFRQ(), NsRatio::den); +} + +void WallClock::SetGuestCNTFRQ(u64 frequency) { + guest_cntfrq.store(frequency, std::memory_order_relaxed); } s64 WallClock::GetGPUTick() const { @@ -419,9 +436,13 @@ u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const { } #endif +u64 WallClock::CPUTickToGuestCNTPCT(u64 cpu_tick) const { + return MultiplyAndDivide64(cpu_tick, GetGuestCNTFRQ(), CPUTickFreq); +} + // Wall clock MUST be initialized AFTER g_cpu_caps // C++ only guarantees ctor init in the order they appear in TU -const WallClock g_wall_clock = [] { +WallClock g_wall_clock = [] { #if defined(ARCHITECTURE_x86_64) auto const& caps = Common::g_cpu_caps; return WallClock(caps.invariant_tsc && caps.tsc_frequency > std::nano::den, caps.tsc_frequency); diff --git a/src/common/cpu_features.h b/src/common/cpu_features.h index 61aa2767ab..31be2dc956 100644 --- a/src/common/cpu_features.h +++ b/src/common/cpu_features.h @@ -7,6 +7,7 @@ #pragma once +#include <atomic> #include <optional> #include <string_view> #include <chrono> @@ -20,11 +21,24 @@ namespace Common { class WallClock { public: static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz + static constexpr u64 Switch2CNTFRQ = 31'250'000; // Switch 2 CNTPCT_EL0 Frequency = 31.25 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 explicit WallClock(bool invariant, u64 rdtsc_frequency_) noexcept; + /// Sets the guest counter's frequency: CNTFRQ, or Switch2CNTFRQ for Switch 2 titles (their SDK + /// converts ticks with a constant 31.25 MHz). Called at boot, before guest code runs. + void SetGuestCNTFRQ(u64 frequency); + + /// @returns The guest counter's frequency (CNTFRQ_EL0). + [[nodiscard]] u64 GetGuestCNTFRQ() const { + return guest_cntfrq.load(std::memory_order_relaxed); + } + + /// @returns Emulated CPU ticks (single core timing) as guest CNTPCT ticks. + [[nodiscard]] u64 CPUTickToGuestCNTPCT(u64 cpu_tick) const; + /// @returns The time in nanoseconds since the construction of this clock. std::chrono::nanoseconds GetTimeNS() const; @@ -34,7 +48,7 @@ public: /// @returns The time in milliseconds since the construction of this clock. std::chrono::milliseconds GetTimeMS() const; - /// @returns The guest CNTPCT ticks since the construction of this clock. + /// @returns The guest CNTPCT ticks (at GetGuestCNTFRQ()) since the construction of this clock. s64 GetCNTPCT() const; /// @returns The guest GPU ticks since the construction of this clock. @@ -91,6 +105,7 @@ public: using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>; using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>; + std::atomic<u64> guest_cntfrq{CNTFRQ}; #if defined(ARCHITECTURE_x86_64) u64 rdtsc_frequency; u64 ns_rdtsc_factor; @@ -98,7 +113,7 @@ public: u64 ms_rdtsc_factor; u64 rdtsc_ns_integer; u64 rdtsc_ns_factor; - u64 cntpct_rdtsc_factor; + std::atomic<u64> cntpct_rdtsc_factor; u64 gputick_rdtsc_factor; bool invariant; #elif defined(HAS_NCE) @@ -106,6 +121,7 @@ public: [[nodiscard]] inline FactorType GetGuestCNTFRQFactor() const { return guest_cntfrq_factor; } + u64 host_cntfrq; FactorType ns_cntfrq_factor; FactorType us_cntfrq_factor; FactorType ms_cntfrq_factor; @@ -183,6 +199,6 @@ std::optional<int> GetProcessorCount(); /// @brief Global cpu caps extern const CPUCaps g_cpu_caps; /// @brief Global wall clock -extern const WallClock g_wall_clock; +extern WallClock g_wall_clock; } // namespace Common |
