Make sure the QPC frequency is cached so the timer can be used
[ardour.git] / libs / pbd / windows_timer_utils.cc
1 /*
2  * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include "pbd/windows_timer_utils.h"
20
21 #include <windows.h>
22 #include <mmsystem.h>
23
24 #include "pbd/compose.h"
25 #include "pbd/debug.h"
26
27 #define DEBUG_TIMING(msg) DEBUG_TRACE (PBD::DEBUG::Timing, msg);
28
29 namespace {
30
31 UINT&
32 timer_resolution ()
33 {
34         static UINT timer_res_ms = 0;
35         return timer_res_ms;
36 }
37
38 }
39
40 namespace PBD {
41
42 namespace MMTIMERS {
43
44 bool
45 get_min_resolution (uint32_t& min_resolution_ms)
46 {
47         TIMECAPS caps;
48
49         if (timeGetDevCaps (&caps, sizeof(TIMECAPS)) != TIMERR_NOERROR) {
50                 DEBUG_TIMING ("Could not get timer device capabilities.\n");
51                 return false;
52         }
53
54         min_resolution_ms = caps.wPeriodMin;
55         return true;
56 }
57
58 bool
59 set_min_resolution ()
60 {
61         uint32_t min_resolution = 0;
62
63         if (!get_min_resolution (min_resolution)) {
64                 return false;
65         }
66
67         if (!set_resolution (min_resolution)) {
68                 return false;
69         }
70         return true;
71 }
72
73 bool
74 set_resolution (uint32_t timer_resolution_ms)
75 {
76         if (timer_resolution() != 0) {
77                 DEBUG_TIMING(
78                     "Timer resolution must be reset before setting new resolution.\n");
79         }
80
81         if (timeBeginPeriod(timer_resolution_ms) != TIMERR_NOERROR) {
82                 DEBUG_TIMING(
83                     string_compose("Could not set timer resolution to %1(ms)\n",
84                                    timer_resolution_ms));
85                 return false;
86         }
87
88         timer_resolution() = timer_resolution_ms;
89
90         DEBUG_TIMING (string_compose ("Multimedia timer resolution set to %1(ms)\n",
91                                       timer_resolution_ms));
92         return true;
93 }
94
95 bool
96 reset_resolution ()
97 {
98         // You must match calls to timeBegin/EndPeriod with the same resolution
99         if (timeEndPeriod(timer_resolution()) != TIMERR_NOERROR) {
100                 DEBUG_TIMING("Could not reset the Timer resolution.\n");
101                 return false;
102         }
103         timer_resolution() = 0;
104         return true;
105 }
106
107 } // namespace MMTIMERS
108
109 namespace {
110
111 bool&
112 qpc_frequency_success ()
113 {
114         static bool success = false;
115         return success;
116 }
117
118 LARGE_INTEGER
119 qpc_frequency ()
120 {
121         LARGE_INTEGER freq;
122         if (QueryPerformanceFrequency(&freq) == 0) {
123                 DEBUG_TIMING ("Failed to determine frequency of QPC\n");
124                 qpc_frequency_success() = false;
125         } else {
126                 qpc_frequency_success() = true;
127         }
128
129         return freq;
130 }
131
132 LARGE_INTEGER
133 qpc_frequency_cached ()
134 {
135         static LARGE_INTEGER frequency = qpc_frequency ();
136         return frequency;
137 }
138
139 bool
140 test_qpc_validity ()
141 {
142         int64_t last_timer_val = PBD::QPC::get_microseconds ();
143         if (last_timer_val < 0) return false;
144
145         for (int i = 0; i < 100000; ++i) {
146                 int64_t timer_val = PBD::QPC::get_microseconds ();
147                 if (timer_val < 0) return false;
148                 // try and test for non-syncronized TSC(AMD K8/etc)
149                 if (timer_val < last_timer_val) return false;
150                 last_timer_val = timer_val;
151         }
152         return true;
153 }
154
155 } // anon namespace
156
157 namespace QPC {
158
159 bool
160 check_timer_valid ()
161 {
162         // setup caching the timer frequency
163         qpc_frequency_cached ();
164         if (!qpc_frequency_success ()) {
165                 return false;
166         }
167         return test_qpc_validity ();
168 }
169
170 int64_t
171 get_microseconds ()
172 {
173         LARGE_INTEGER current_val;
174
175         if (qpc_frequency_success()) {
176                 // MS docs say this will always succeed for systems >= XP but it may
177                 // not return a monotonic value with non-invariant TSC's etc
178                 if (QueryPerformanceCounter(&current_val) != 0) {
179                         return (int64_t)(((double)current_val.QuadPart) /
180                                          ((double)qpc_frequency_cached().QuadPart) * 1000000.0);
181                 }
182         }
183         DEBUG_TIMING ("Could not get QPC timer\n");
184         return -1;
185 }
186
187 } // namespace QPC
188
189 int64_t
190 get_microseconds ()
191 {
192         qpc_frequency_cached();
193         if (qpc_frequency_success()) {
194                 return QPC::get_microseconds ();
195         }
196         // For XP systems that don't support a high-res performance counter
197         return g_get_monotonic_time ();
198 }
199
200 } // namespace PBD