summaryrefslogtreecommitdiff
path: root/Source/Core/Common/Timer.cpp
blob: 957073426b264f9af793b8a5aeacad57d71c481b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "Common/Timer.h"

#include <chrono>
#include <thread>
#include "Common/CommonFuncs.h"

#ifdef _WIN32
#include <Windows.h>
#include <timeapi.h>
#endif

#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"

namespace Common
{
template <typename Clock, typename Duration>
static typename Clock::rep time_now()
{
  return std::chrono::time_point_cast<Duration>(Clock::now()).time_since_epoch().count();
}

template <typename Duration>
static auto steady_time_now()
{
  return time_now<std::chrono::steady_clock, Duration>();
}

u64 Timer::NowUs()
{
  return steady_time_now<std::chrono::microseconds>();
}

u64 Timer::NowMs()
{
  return steady_time_now<std::chrono::milliseconds>();
}

void Timer::Start()
{
  m_start_ms = NowMs();
  m_end_ms = 0;
  m_running = true;
}

void Timer::StartWithOffset(u64 offset)
{
  Start();
  m_start_ms -= offset;
}

void Timer::Stop()
{
  m_end_ms = NowMs();
  m_running = false;
}

u64 Timer::ElapsedMs() const
{
  const u64 end = m_running ? NowMs() : m_end_ms;
  // Can handle up to 1 rollover event (underflow produces correct result)
  // If Start() has never been called, will return 0
  return end - m_start_ms;
}

u64 Timer::GetLocalTimeSinceJan1970()
{
#ifdef _MSC_VER
  std::chrono::zoned_seconds seconds(
      std::chrono::current_zone(),
      std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now()));
  return seconds.get_local_time().time_since_epoch().count();
#else
  time_t sysTime, tzDiff, tzDST;
  time(&sysTime);
  tm* gmTime = localtime(&sysTime);

  // Account for DST where needed
  if (gmTime->tm_isdst == 1)
    tzDST = 3600;
  else
    tzDST = 0;

  // Lazy way to get local time in sec
  gmTime = gmtime(&sysTime);
  tzDiff = sysTime - mktime(gmTime);

  return static_cast<u64>(sysTime + tzDiff + tzDST);
#endif
}

// This is requested by Timer::IncreaseResolution on Windows.
// On Linux/other we hope/assume we already have 1ms scheduling granularity.
static constexpr int TIMER_RESOLUTION_MS = 1;

void Timer::IncreaseResolution()
{
#ifdef _WIN32
  // Disable execution speed and timer resolution throttling process-wide.
  // This mainly will keep Dolphin marked as high performance if it's in the background. The OS
  // should make it high performance if it's in the foreground anyway (or for some specific
  // threads e.g. audio).
  // This is best-effort (i.e. the call may fail on older versions of Windows, where such throttling
  // doesn't exist, anyway), and we don't bother reverting once set.
  // This adjusts behavior on CPUs with "performance" and "efficiency" cores
  PROCESS_POWER_THROTTLING_STATE PowerThrottling{};
  PowerThrottling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
  PowerThrottling.ControlMask =
      PROCESS_POWER_THROTTLING_EXECUTION_SPEED | PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
  PowerThrottling.StateMask = 0;
  SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &PowerThrottling,
                        sizeof(PowerThrottling));

  // Not actually sure how useful this is these days.. :')
  timeBeginPeriod(TIMER_RESOLUTION_MS);
#endif
}

void Timer::RestoreResolution()
{
#ifdef _WIN32
  timeEndPeriod(TIMER_RESOLUTION_MS);
#endif
}

PrecisionTimer::PrecisionTimer()
{
#if defined(_WIN32)
  // "TIMER_HIGH_RESOLUTION" requires Windows 10, version 1803, and later.
  m_timer_handle =
      CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);

  if (m_timer_handle == NULL)
  {
    ERROR_LOG_FMT(COMMON, "CREATE_WAITABLE_TIMER_HIGH_RESOLUTION: Error:{}", GetLastError());

    // Create a normal timer if "HIGH_RESOLUTION" isn't available.
    m_timer_handle = CreateWaitableTimerExW(NULL, NULL, 0, TIMER_ALL_ACCESS);
    if (m_timer_handle == NULL)
      ERROR_LOG_FMT(COMMON, "CreateWaitableTimerExW: Error:{}", GetLastError());
  }
#endif
}

PrecisionTimer::~PrecisionTimer()
{
#if defined(_WIN32)
  CloseHandle(m_timer_handle);
#endif
}

void PrecisionTimer::SleepUntil(Clock::time_point target)
{
  constexpr auto SPIN_TIME =
      std::chrono::milliseconds{TIMER_RESOLUTION_MS} + std::chrono::microseconds{20};

#if defined(_WIN32)
  while (true)
  {
    // SetWaitableTimerEx takes time in "100 nanosecond intervals".
    using TimerDuration = std::chrono::duration<LONGLONG, std::ratio<100, std::nano::den>::type>;

    // Apparently waiting longer than the timer resolution gives terrible accuracy.
    // We'll wait in steps of 95% of the time period.
    constexpr auto MAX_TICKS =
        duration_cast<TimerDuration>(std::chrono::milliseconds{TIMER_RESOLUTION_MS}) * 95 / 100;

    const auto wait_time = target - Clock::now() - SPIN_TIME;
    const auto ticks = std::min(duration_cast<TimerDuration>(wait_time), MAX_TICKS).count();
    if (ticks <= 0)
      break;

    const LARGE_INTEGER due_time{.QuadPart = -ticks};
    SetWaitableTimerEx(m_timer_handle, &due_time, 0, NULL, NULL, NULL, 0);
    WaitForSingleObject(m_timer_handle, INFINITE);
  }
#else
  // Sleeping on Linux generally isn't as terrible as it is on Windows.
  std::this_thread::sleep_until(target - SPIN_TIME);
#endif

  // Spin for the remaining time.
  while (Clock::now() < target)
  {
#if defined(_WIN32)
    YieldProcessor();
#else
    std::this_thread::yield();
#endif
  }
}

// Results are appropriately slewed on Linux, but not on Windows, macOS, or FreeBSD.
// Clocks with that functionality seem to not be available there.
auto SteadyAwakeClock::now() -> time_point
{
#if defined(_WIN32)
  // The count is system time "in units of 100 nanoseconds".
  using InterruptDuration = std::chrono::duration<ULONGLONG, std::ratio<100, std::nano::den>::type>;

  ULONGLONG interrupt_time{};
  if (!QueryUnbiasedInterruptTime(&interrupt_time))
    ERROR_LOG_FMT(COMMON, "QueryUnbiasedInterruptTime");

  return time_point{InterruptDuration{interrupt_time}};
#else
  // Note that Linux's CLOCK_MONOTONIC "does not count time that the system is suspended".
  // This is in contrast to the behavior on macOS and FreeBSD.
  static constexpr auto clock_id =
#if defined(__linux__)
      CLOCK_MONOTONIC;
#elif defined(__APPLE__)
      CLOCK_UPTIME_RAW;
#else
      CLOCK_UPTIME;
#endif

  timespec ts{};
  if (clock_gettime(clock_id, &ts) != 0)
    ERROR_LOG_FMT(COMMON, "clock_gettime: {}", LastStrerrorString());

  return time_point{std::chrono::seconds{ts.tv_sec} + std::chrono::nanoseconds{ts.tv_nsec}};
#endif
}

}  // Namespace Common