From 0218eacfd560d0ce48b3ffe64d279ee85bbf1347 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:55:20 -0400 Subject: 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= 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. --- src/core/core.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'src/core/core.cpp') diff --git a/src/core/core.cpp b/src/core/core.cpp index f3d224b1ce..3728ccf8f7 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -2,11 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include +#include #include #include #include #include "game_settings.h" +#include "common/cpu_features.h" #include "audio_core/audio_core.h" #include "common/fs/fs.h" #include "common/logging.h" @@ -313,16 +315,33 @@ struct System::Impl { fs_controller.InitTempStorage(); } + const bool switch2_program = + virtual_filesystem != nullptr && + IsSwitch2Program(GetGameFileFromPath(virtual_filesystem, filepath)); + // Switch 2 titles size their heap from the total memory and need the 12 GiB layout. Set it // as a per-game value, so RestoreGlobalState brings back the configured layout afterwards. - if (virtual_filesystem != nullptr && - Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_12Gb && - IsSwitch2Program(GetGameFileFromPath(virtual_filesystem, filepath))) { + if (switch2_program && + Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_12Gb) { LOG_INFO(Core, "Switch 2 program: using the 12 GiB memory layout"); Settings::values.memory_layout_mode.SetGlobal(false); Settings::values.memory_layout_mode.SetValue(Settings::MemoryLayout::Memory_12Gb); } + // The Switch 2 counter runs at 31.25 MHz and its SDK converts ticks with that constant + // (nn::os::GetSystemTickFrequency, TickManager::ConvertToTimeSpan/ConvertToTick). At 19.2 MHz + // every tick-based time in the game was off by 1.63x: elapsed times read short and a frame + // deadline in ticks waited ~27 ms. EDEN_NX2_CNTFRQ= overrides it (A/B tests). + static const u64 forced_cntfrq = [] { + const char* value = std::getenv("EDEN_NX2_CNTFRQ"); + return value != nullptr ? std::strtoull(value, nullptr, 10) : 0ULL; + }(); + const u64 cntfrq = forced_cntfrq != 0 ? forced_cntfrq + : switch2_program ? Common::WallClock::Switch2CNTFRQ + : Common::WallClock::CNTFRQ; + Common::g_wall_clock.SetGuestCNTFRQ(cntfrq); + LOG_INFO(Core, "Guest counter frequency: {} Hz", cntfrq); + InitializeKernel(system); if (params.applet_type == Service::AM::AppletType::Application) { -- cgit v1.2.3