summaryrefslogtreecommitdiffstats
path: root/src/libs/dxvk-native-1.9.2a/src/util/util_fps_limiter.cpp
blob: 5256c8d667d6c4079110de84ecd81d23ecf9ce33 (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
#include <thread>

#include "thread.h"
#include "util_env.h"
#include "util_fps_limiter.h"
#include "util_string.h"

#include "./log/log.h"

namespace dxvk {
  
  FpsLimiter::FpsLimiter() {
    std::string env = env::getEnvVar("DXVK_FRAME_RATE");

    if (!env.empty()) {
      try {
        setTargetFrameRate(std::stod(env));
        m_envOverride = true;
      } catch (const std::invalid_argument&) {
        // no-op
      }
    }
  }


  FpsLimiter::~FpsLimiter() {

  }


  void FpsLimiter::setTargetFrameRate(double frameRate) {
    std::lock_guard<dxvk::mutex> lock(m_mutex);

    if (!m_envOverride) {
      m_targetInterval = frameRate > 0.0
        ? NtTimerDuration(int64_t(double(NtTimerDuration::period::den) / frameRate))
        : NtTimerDuration::zero();

      if (isEnabled() && !m_initialized)
        initialize();
    }
  }


  void FpsLimiter::setDisplayRefreshRate(double refreshRate) {
    std::lock_guard<dxvk::mutex> lock(m_mutex);

    m_refreshInterval = refreshRate > 0.0
      ? NtTimerDuration(int64_t(double(NtTimerDuration::period::den) / refreshRate))
      : NtTimerDuration::zero();
  }


  void FpsLimiter::delay(bool vsyncEnabled) {
    std::lock_guard<dxvk::mutex> lock(m_mutex);

    if (!isEnabled())
      return;

    // If the swap chain is known to have vsync enabled and the
    // refresh rate is similar to the target frame rate, disable
    // the limiter so it does not screw up frame times
    if (vsyncEnabled && !m_envOverride
     && m_refreshInterval * 100 > m_targetInterval * 97)
      return;

    auto t0 = m_lastFrame;
    auto t1 = dxvk::high_resolution_clock::now();

    auto frameTime = std::chrono::duration_cast<NtTimerDuration>(t1 - t0);

    if (frameTime * 100 > m_targetInterval * 103 - m_deviation * 100) {
      // If we have a slow frame, reset the deviation since we
      // do not want to compensate for low performance later on
      m_deviation = NtTimerDuration::zero();
    } else {
      // Don't call sleep if the amount of time to sleep is shorter
      // than the time the function calls are likely going to take
      NtTimerDuration sleepDuration = m_targetInterval - m_deviation - frameTime;
      t1 = sleep(t1, sleepDuration);

      // Compensate for any sleep inaccuracies in the next frame, and
      // limit cumulative deviation in order to avoid stutter in case we
      // have a number of slow frames immediately followed by a fast one.
      frameTime = std::chrono::duration_cast<NtTimerDuration>(t1 - t0);
      m_deviation += frameTime - m_targetInterval;
      m_deviation = std::min(m_deviation, m_targetInterval / 16);
    }

    m_lastFrame = t1;
  }


  FpsLimiter::TimePoint FpsLimiter::sleep(TimePoint t0, NtTimerDuration duration) {
    if (duration <= NtTimerDuration::zero())
      return t0;

    // On wine, we can rely on NtDelayExecution waiting for more or
    // less exactly the desired amount of time, and we want to avoid
    // spamming QueryPerformanceCounter for performance reasons.
    // On Windows, we busy-wait for the last couple of milliseconds
    // since sleeping is highly inaccurate and inconsistent.
    NtTimerDuration sleepThreshold = m_sleepThreshold;

    if (m_sleepGranularity != NtTimerDuration::zero())
      sleepThreshold += duration / 6;

    NtTimerDuration remaining = duration;
    TimePoint t1 = t0;

    while (remaining > sleepThreshold) {
      NtTimerDuration sleepDuration = remaining - sleepThreshold;

      if (NtDelayExecution) {
        LARGE_INTEGER ticks;
        ticks.QuadPart = -sleepDuration.count();

        NtDelayExecution(FALSE, &ticks);
      } else {
        std::this_thread::sleep_for(sleepDuration);
      }

      t1 = dxvk::high_resolution_clock::now();
      remaining -= std::chrono::duration_cast<NtTimerDuration>(t1 - t0);
      t0 = t1;
    }

    // Busy-wait until we have slept long enough
    while (remaining > NtTimerDuration::zero()) {
      t1 = dxvk::high_resolution_clock::now();
      remaining -= std::chrono::duration_cast<NtTimerDuration>(t1 - t0);
      t0 = t1;
    }

    return t1;
  }


  void FpsLimiter::initialize() {
#ifdef _WIN32
    HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");

    if (ntdll) {
      NtDelayExecution = reinterpret_cast<NtDelayExecutionProc>(
        ::GetProcAddress(ntdll, "NtDelayExecution"));
      auto NtQueryTimerResolution = reinterpret_cast<NtQueryTimerResolutionProc>(
        ::GetProcAddress(ntdll, "NtQueryTimerResolution"));
      auto NtSetTimerResolution = reinterpret_cast<NtSetTimerResolutionProc>(
        ::GetProcAddress(ntdll, "NtSetTimerResolution"));

      ULONG min, max, cur;

      // Wine's implementation of these functions is a stub as of 6.10, which is fine
      // since it uses select() in NtDelayExecution. This is only relevant for Windows.
      if (NtQueryTimerResolution && !NtQueryTimerResolution(&min, &max, &cur)) {
        m_sleepGranularity = NtTimerDuration(cur);

        if (NtSetTimerResolution && !NtSetTimerResolution(max, TRUE, &cur)) {
          Logger::info(str::format("Setting timer interval to ", (double(max) / 10.0), " us"));
          m_sleepGranularity = NtTimerDuration(max);
        }
      }
    } else
#endif
    {
      // Assume 1ms sleep granularity by default
      m_sleepGranularity = NtTimerDuration(10000);
    }

    m_sleepThreshold = 4 * m_sleepGranularity;
    m_lastFrame = dxvk::high_resolution_clock::now();
    m_initialized = true;
  }

}