Просмотр исходного кода

Merge pull request #9889 from Morph1984/time-is-ticking

core_timing: Reduce CPU usage on Windows
liamwhite 3 лет назад
Родитель
Сommit
a7792e5ff8

+ 2 - 2
CMakeLists.txt

@@ -477,8 +477,8 @@ if (APPLE)
     find_library(COCOA_LIBRARY Cocoa)
     set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
 elseif (WIN32)
-    # WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista)
-    add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600)
+    # Target Windows 10
+    add_definitions(-D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00)
     set(PLATFORM_LIBRARIES winmm ws2_32 iphlpapi)
     if (MINGW)
         # PSAPI is the Process Status API

+ 0 - 6
dist/yuzu.manifest

@@ -36,12 +36,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
     <application>
       <!-- Windows 10 -->
       <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
-      <!-- Windows 8.1 -->
-      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
-      <!-- Windows 8 -->
-      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
-      <!-- Windows 7 -->
-      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
     </application>
   </compatibility>
   <trustInfo

+ 10 - 0
src/common/CMakeLists.txt

@@ -113,6 +113,8 @@ add_library(common STATIC
     socket_types.h
     spin_lock.cpp
     spin_lock.h
+    steady_clock.cpp
+    steady_clock.h
     stream.cpp
     stream.h
     string_util.cpp
@@ -142,6 +144,14 @@ add_library(common STATIC
     zstd_compression.h
 )
 
+if (WIN32)
+  target_sources(common PRIVATE
+    windows/timer_resolution.cpp
+    windows/timer_resolution.h
+  )
+  target_link_libraries(common PRIVATE ntdll)
+endif()
+
 if(ARCHITECTURE_x86_64)
     target_sources(common
         PRIVATE

+ 56 - 0
src/common/steady_clock.cpp

@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#if defined(_WIN32)
+#include <windows.h>
+#else
+#include <time.h>
+#endif
+
+#include "common/steady_clock.h"
+
+namespace Common {
+
+#ifdef _WIN32
+static s64 WindowsQueryPerformanceFrequency() {
+    LARGE_INTEGER frequency;
+    QueryPerformanceFrequency(&frequency);
+    return frequency.QuadPart;
+}
+
+static s64 WindowsQueryPerformanceCounter() {
+    LARGE_INTEGER counter;
+    QueryPerformanceCounter(&counter);
+    return counter.QuadPart;
+}
+#endif
+
+SteadyClock::time_point SteadyClock::Now() noexcept {
+#if defined(_WIN32)
+    static const auto freq = WindowsQueryPerformanceFrequency();
+    const auto counter = WindowsQueryPerformanceCounter();
+
+    // 10 MHz is a very common QPC frequency on modern PCs.
+    // Optimizing for this specific frequency can double the performance of
+    // this function by avoiding the expensive frequency conversion path.
+    static constexpr s64 TenMHz = 10'000'000;
+
+    if (freq == TenMHz) [[likely]] {
+        static_assert(period::den % TenMHz == 0);
+        static constexpr s64 Multiplier = period::den / TenMHz;
+        return time_point{duration{counter * Multiplier}};
+    }
+
+    const auto whole = (counter / freq) * period::den;
+    const auto part = (counter % freq) * period::den / freq;
+    return time_point{duration{whole + part}};
+#elif defined(__APPLE__)
+    return time_point{duration{clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)}};
+#else
+    timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    return time_point{std::chrono::seconds{ts.tv_sec} + std::chrono::nanoseconds{ts.tv_nsec}};
+#endif
+}
+
+}; // namespace Common

+ 23 - 0
src/common/steady_clock.h

@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <chrono>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+struct SteadyClock {
+    using rep = s64;
+    using period = std::nano;
+    using duration = std::chrono::nanoseconds;
+    using time_point = std::chrono::time_point<SteadyClock>;
+
+    static constexpr bool is_steady = true;
+
+    [[nodiscard]] static time_point Now() noexcept;
+};
+
+} // namespace Common

+ 16 - 23
src/common/wall_clock.cpp

@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include "common/steady_clock.h"
 #include "common/uint128.h"
 #include "common/wall_clock.h"
 
@@ -11,45 +12,32 @@
 
 namespace Common {
 
-using base_timer = std::chrono::steady_clock;
-using base_time_point = std::chrono::time_point<base_timer>;
-
 class StandardWallClock final : public WallClock {
 public:
     explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_)
-        : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, false) {
-        start_time = base_timer::now();
-    }
+        : WallClock{emulated_cpu_frequency_, emulated_clock_frequency_, false},
+          start_time{SteadyClock::Now()} {}
 
     std::chrono::nanoseconds GetTimeNS() override {
-        base_time_point current = base_timer::now();
-        auto elapsed = current - start_time;
-        return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed);
+        return SteadyClock::Now() - start_time;
     }
 
     std::chrono::microseconds GetTimeUS() override {
-        base_time_point current = base_timer::now();
-        auto elapsed = current - start_time;
-        return std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
+        return std::chrono::duration_cast<std::chrono::microseconds>(GetTimeNS());
     }
 
     std::chrono::milliseconds GetTimeMS() override {
-        base_time_point current = base_timer::now();
-        auto elapsed = current - start_time;
-        return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
+        return std::chrono::duration_cast<std::chrono::milliseconds>(GetTimeNS());
     }
 
     u64 GetClockCycles() override {
-        std::chrono::nanoseconds time_now = GetTimeNS();
-        const u128 temporary =
-            Common::Multiply64Into128(time_now.count(), emulated_clock_frequency);
-        return Common::Divide128On32(temporary, 1000000000).first;
+        const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_clock_frequency);
+        return Common::Divide128On32(temp, NS_RATIO).first;
     }
 
     u64 GetCPUCycles() override {
-        std::chrono::nanoseconds time_now = GetTimeNS();
-        const u128 temporary = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency);
-        return Common::Divide128On32(temporary, 1000000000).first;
+        const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_cpu_frequency);
+        return Common::Divide128On32(temp, NS_RATIO).first;
     }
 
     void Pause([[maybe_unused]] bool is_paused) override {
@@ -57,7 +45,7 @@ public:
     }
 
 private:
-    base_time_point start_time;
+    SteadyClock::time_point start_time;
 };
 
 #ifdef ARCHITECTURE_x86_64
@@ -93,4 +81,9 @@ std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
 
 #endif
 
+std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
+                                                   u64 emulated_clock_frequency) {
+    return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
+}
+
 } // namespace Common

+ 3 - 0
src/common/wall_clock.h

@@ -55,4 +55,7 @@ private:
 [[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
                                                                  u64 emulated_clock_frequency);
 
+[[nodiscard]] std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
+                                                                 u64 emulated_clock_frequency);
+
 } // namespace Common

+ 109 - 0
src/common/windows/timer_resolution.cpp

@@ -0,0 +1,109 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <windows.h>
+
+#include "common/windows/timer_resolution.h"
+
+extern "C" {
+// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtQueryTimerResolution.html
+NTSYSAPI LONG NTAPI NtQueryTimerResolution(PULONG MinimumResolution, PULONG MaximumResolution,
+                                           PULONG CurrentResolution);
+
+// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html
+NTSYSAPI LONG NTAPI NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution,
+                                         PULONG CurrentResolution);
+
+// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FThread%2FNtDelayExecution.html
+NTSYSAPI LONG NTAPI NtDelayExecution(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval);
+}
+
+// Defines for compatibility with older Windows 10 SDKs.
+
+#ifndef PROCESS_POWER_THROTTLING_EXECUTION_SPEED
+#define PROCESS_POWER_THROTTLING_EXECUTION_SPEED 0x1
+#endif
+#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION
+#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x4
+#endif
+
+namespace Common::Windows {
+
+namespace {
+
+using namespace std::chrono;
+
+constexpr nanoseconds ToNS(ULONG hundred_ns) {
+    return nanoseconds{hundred_ns * 100};
+}
+
+constexpr ULONG ToHundredNS(nanoseconds ns) {
+    return static_cast<ULONG>(ns.count()) / 100;
+}
+
+struct TimerResolution {
+    std::chrono::nanoseconds minimum;
+    std::chrono::nanoseconds maximum;
+    std::chrono::nanoseconds current;
+};
+
+TimerResolution GetTimerResolution() {
+    ULONG MinimumTimerResolution;
+    ULONG MaximumTimerResolution;
+    ULONG CurrentTimerResolution;
+    NtQueryTimerResolution(&MinimumTimerResolution, &MaximumTimerResolution,
+                           &CurrentTimerResolution);
+    return {
+        .minimum{ToNS(MinimumTimerResolution)},
+        .maximum{ToNS(MaximumTimerResolution)},
+        .current{ToNS(CurrentTimerResolution)},
+    };
+}
+
+void SetHighQoS() {
+    // https://learn.microsoft.com/en-us/windows/win32/procthread/quality-of-service
+    PROCESS_POWER_THROTTLING_STATE PowerThrottling{
+        .Version{PROCESS_POWER_THROTTLING_CURRENT_VERSION},
+        .ControlMask{PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
+                     PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION},
+        .StateMask{},
+    };
+    SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &PowerThrottling,
+                          sizeof(PROCESS_POWER_THROTTLING_STATE));
+}
+
+} // Anonymous namespace
+
+nanoseconds GetMinimumTimerResolution() {
+    return GetTimerResolution().minimum;
+}
+
+nanoseconds GetMaximumTimerResolution() {
+    return GetTimerResolution().maximum;
+}
+
+nanoseconds GetCurrentTimerResolution() {
+    return GetTimerResolution().current;
+}
+
+nanoseconds SetCurrentTimerResolution(nanoseconds timer_resolution) {
+    // Set the timer resolution, and return the current timer resolution.
+    const auto DesiredTimerResolution = ToHundredNS(timer_resolution);
+    ULONG CurrentTimerResolution;
+    NtSetTimerResolution(DesiredTimerResolution, TRUE, &CurrentTimerResolution);
+    return ToNS(CurrentTimerResolution);
+}
+
+nanoseconds SetCurrentTimerResolutionToMaximum() {
+    SetHighQoS();
+    return SetCurrentTimerResolution(GetMaximumTimerResolution());
+}
+
+void SleepForOneTick() {
+    LARGE_INTEGER DelayInterval{
+        .QuadPart{-1},
+    };
+    NtDelayExecution(FALSE, &DelayInterval);
+}
+
+} // namespace Common::Windows

+ 38 - 0
src/common/windows/timer_resolution.h

@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <chrono>
+
+namespace Common::Windows {
+
+/// Returns the minimum (least precise) supported timer resolution in nanoseconds.
+std::chrono::nanoseconds GetMinimumTimerResolution();
+
+/// Returns the maximum (most precise) supported timer resolution in nanoseconds.
+std::chrono::nanoseconds GetMaximumTimerResolution();
+
+/// Returns the current timer resolution in nanoseconds.
+std::chrono::nanoseconds GetCurrentTimerResolution();
+
+/**
+ * Sets the current timer resolution.
+ *
+ * @param timer_resolution Timer resolution in nanoseconds.
+ *
+ * @returns The current timer resolution.
+ */
+std::chrono::nanoseconds SetCurrentTimerResolution(std::chrono::nanoseconds timer_resolution);
+
+/**
+ * Sets the current timer resolution to the maximum supported timer resolution.
+ *
+ * @returns The current timer resolution.
+ */
+std::chrono::nanoseconds SetCurrentTimerResolutionToMaximum();
+
+/// Sleep for one tick of the current timer resolution.
+void SleepForOneTick();
+
+} // namespace Common::Windows

+ 12 - 5
src/common/x64/native_clock.cpp

@@ -6,6 +6,7 @@
 #include <thread>
 
 #include "common/atomic_ops.h"
+#include "common/steady_clock.h"
 #include "common/uint128.h"
 #include "common/x64/native_clock.h"
 
@@ -39,6 +40,12 @@ static u64 FencedRDTSC() {
 }
 #endif
 
+template <u64 Nearest>
+static u64 RoundToNearest(u64 value) {
+    const auto mod = value % Nearest;
+    return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
+}
+
 u64 EstimateRDTSCFrequency() {
     // Discard the first result measuring the rdtsc.
     FencedRDTSC();
@@ -46,18 +53,18 @@ u64 EstimateRDTSCFrequency() {
     FencedRDTSC();
 
     // Get the current time.
-    const auto start_time = std::chrono::steady_clock::now();
+    const auto start_time = Common::SteadyClock::Now();
     const u64 tsc_start = FencedRDTSC();
-    // Wait for 200 milliseconds.
-    std::this_thread::sleep_for(std::chrono::milliseconds{200});
-    const auto end_time = std::chrono::steady_clock::now();
+    // Wait for 250 milliseconds.
+    std::this_thread::sleep_for(std::chrono::milliseconds{250});
+    const auto end_time = Common::SteadyClock::Now();
     const u64 tsc_end = FencedRDTSC();
     // Calculate differences.
     const u64 timer_diff = static_cast<u64>(
         std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
     const u64 tsc_diff = tsc_end - tsc_start;
     const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
-    return tsc_freq;
+    return RoundToNearest<1000>(tsc_freq);
 }
 
 namespace X64 {

+ 33 - 22
src/core/core_timing.cpp

@@ -6,6 +6,10 @@
 #include <string>
 #include <tuple>
 
+#ifdef _WIN32
+#include "common/windows/timer_resolution.h"
+#endif
+
 #include "common/microprofile.h"
 #include "core/core_timing.h"
 #include "core/core_timing_util.h"
@@ -38,7 +42,8 @@ struct CoreTiming::Event {
 };
 
 CoreTiming::CoreTiming()
-    : clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {}
+    : cpu_clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)},
+      event_clock{Common::CreateStandardWallClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {}
 
 CoreTiming::~CoreTiming() {
     Reset();
@@ -185,15 +190,15 @@ void CoreTiming::ResetTicks() {
 }
 
 u64 CoreTiming::GetCPUTicks() const {
-    if (is_multicore) {
-        return clock->GetCPUCycles();
+    if (is_multicore) [[likely]] {
+        return cpu_clock->GetCPUCycles();
     }
     return ticks;
 }
 
 u64 CoreTiming::GetClockTicks() const {
-    if (is_multicore) {
-        return clock->GetClockCycles();
+    if (is_multicore) [[likely]] {
+        return cpu_clock->GetClockCycles();
     }
     return CpuCyclesToClockCycles(ticks);
 }
@@ -252,21 +257,20 @@ void CoreTiming::ThreadLoop() {
             const auto next_time = Advance();
             if (next_time) {
                 // There are more events left in the queue, wait until the next event.
-                const auto wait_time = *next_time - GetGlobalTimeNs().count();
+                auto wait_time = *next_time - GetGlobalTimeNs().count();
                 if (wait_time > 0) {
 #ifdef _WIN32
-                    // Assume a timer resolution of 1ms.
-                    static constexpr s64 TimerResolutionNS = 1000000;
+                    const auto timer_resolution_ns =
+                        Common::Windows::GetCurrentTimerResolution().count();
 
-                    // Sleep in discrete intervals of the timer resolution, and spin the rest.
-                    const auto sleep_time = wait_time - (wait_time % TimerResolutionNS);
-                    if (sleep_time > 0) {
-                        event.WaitFor(std::chrono::nanoseconds(sleep_time));
-                    }
+                    while (!paused && !event.IsSet() && wait_time > 0) {
+                        wait_time = *next_time - GetGlobalTimeNs().count();
 
-                    while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) {
-                        // Yield to reduce thread starvation.
-                        std::this_thread::yield();
+                        if (wait_time >= timer_resolution_ns) {
+                            Common::Windows::SleepForOneTick();
+                        } else {
+                            std::this_thread::yield();
+                        }
                     }
 
                     if (event.IsSet()) {
@@ -285,9 +289,9 @@ void CoreTiming::ThreadLoop() {
         }
 
         paused_set = true;
-        clock->Pause(true);
+        event_clock->Pause(true);
         pause_event.Wait();
-        clock->Pause(false);
+        event_clock->Pause(false);
     }
 }
 
@@ -303,16 +307,23 @@ void CoreTiming::Reset() {
     has_started = false;
 }
 
+std::chrono::nanoseconds CoreTiming::GetCPUTimeNs() const {
+    if (is_multicore) [[likely]] {
+        return cpu_clock->GetTimeNS();
+    }
+    return CyclesToNs(ticks);
+}
+
 std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
-    if (is_multicore) {
-        return clock->GetTimeNS();
+    if (is_multicore) [[likely]] {
+        return event_clock->GetTimeNS();
     }
     return CyclesToNs(ticks);
 }
 
 std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
-    if (is_multicore) {
-        return clock->GetTimeUS();
+    if (is_multicore) [[likely]] {
+        return event_clock->GetTimeUS();
     }
     return CyclesToUs(ticks);
 }

+ 5 - 1
src/core/core_timing.h

@@ -122,6 +122,9 @@ public:
     /// Returns current time in emulated in Clock cycles
     u64 GetClockTicks() const;
 
+    /// Returns current time in nanoseconds.
+    std::chrono::nanoseconds GetCPUTimeNs() const;
+
     /// Returns current time in microseconds.
     std::chrono::microseconds GetGlobalTimeUs() const;
 
@@ -139,7 +142,8 @@ private:
 
     void Reset();
 
-    std::unique_ptr<Common::WallClock> clock;
+    std::unique_ptr<Common::WallClock> cpu_clock;
+    std::unique_ptr<Common::WallClock> event_clock;
 
     s64 global_timer = 0;
 

+ 3 - 5
src/core/hardware_properties.h

@@ -13,11 +13,9 @@ namespace Core {
 
 namespace Hardware {
 
-// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
-// The exact value used is of course unverified.
-constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch cpu frequency is 1020MHz un/docked
-constexpr u64 CNTFREQ = 19200000;           // Switch's hardware clock speed
-constexpr u32 NUM_CPU_CORES = 4;            // Number of CPU Cores
+constexpr u64 BASE_CLOCK_RATE = 1'020'000'000; // Default CPU Frequency = 1020 MHz
+constexpr u64 CNTFREQ = 19'200'000;            // CNTPCT_EL0 Frequency = 19.2 MHz
+constexpr u32 NUM_CPU_CORES = 4;               // Number of CPU Cores
 
 // Virtual to Physical core map.
 constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{

+ 1 - 1
src/video_core/gpu.cpp

@@ -197,7 +197,7 @@ struct GPU::Impl {
         constexpr u64 gpu_ticks_num = 384;
         constexpr u64 gpu_ticks_den = 625;
 
-        u64 nanoseconds = system.CoreTiming().GetGlobalTimeNs().count();
+        u64 nanoseconds = system.CoreTiming().GetCPUTimeNs().count();
         if (Settings::values.use_fast_gpu_time.GetValue()) {
             nanoseconds /= 256;
         }

+ 9 - 0
src/yuzu/main.cpp

@@ -91,6 +91,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
 #include "common/microprofile.h"
 #include "common/scm_rev.h"
 #include "common/scope_exit.h"
+#ifdef _WIN32
+#include "common/windows/timer_resolution.h"
+#endif
 #ifdef ARCHITECTURE_x86_64
 #include "common/x64/cpu_detect.h"
 #endif
@@ -377,6 +380,12 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
     LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",
              Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB});
     LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB});
+#ifdef _WIN32
+    LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms",
+             std::chrono::duration_cast<std::chrono::duration<f64, std::milli>>(
+                 Common::Windows::SetCurrentTimerResolutionToMaximum())
+                 .count());
+#endif
     UpdateWindowTitle();
 
     show();

+ 4 - 0
src/yuzu_cmd/yuzu.cpp

@@ -42,6 +42,8 @@
 #include <windows.h>
 
 #include <shellapi.h>
+
+#include "common/windows/timer_resolution.h"
 #endif
 
 #undef _UNICODE
@@ -314,6 +316,8 @@ int main(int argc, char** argv) {
 
 #ifdef _WIN32
     LocalFree(argv_w);
+
+    Common::Windows::SetCurrentTimerResolutionToMaximum();
 #endif
 
     MicroProfileOnThreadCreate("EmuThread");