win32: Windows 10: timeBeginPeriod on demand

Before this commit, timeBeginPeriod(1) was set once when mpv starts,
and the timers remained hi-res till mpv exits.

Now we do the same as before on Windows version < 10.
On Windows 10+ we now use timeBeginPeriod if needed, per timeout.

To force a mode regardless of Windows version, set env MPV_HRT:
- "always":  the old behavior - hires timers as long as mpv runs.
- "perwait": sets 1ms timer resolution if timeout <= 50ms.
- "never":   don't use timeBeginPeriod at all.

It was observed that on Windows 10 we lose about 0.5ms accuracy of
timeouts with "perwait" mode (acceptable), but otherwise it works
well for continuous timeouts (one after the other) and random ones.

On Windows 7 with "perwait": continous timeouts are accurate, but
random timeouts (after some time without timeouts) have bad
accuracy - roughly 16ms resolution instead of the requested 1ms.

Windows 8 was not tested, so to err on the side of caution, we keep
the legacy behavior "always" by default.
This commit is contained in:
Avi Halachmi (:avih) 2021-09-15 17:04:17 +03:00 committed by James Ross-Gowan
parent 7b9e024f37
commit 930b483a68
3 changed files with 72 additions and 1 deletions

View File

@ -19,12 +19,39 @@
#include <sys/time.h>
#include <mmsystem.h>
#include <stdlib.h>
#include <versionhelpers.h>
#include "timer.h"
#include "config.h"
static LARGE_INTEGER perf_freq;
// ms values
static int hires_max = 50;
static int hires_res = 1;
int mp_start_hires_timers(int wait_ms)
{
#if !HAVE_UWP
// policy: request hires_res ms resolution if wait < hires_max ms
if (wait_ms > 0 && wait_ms <= hires_max &&
timeBeginPeriod(hires_res) == TIMERR_NOERROR)
{
return hires_res;
}
#endif
return 0;
}
void mp_end_hires_timers(int res_ms)
{
#if !HAVE_UWP
if (res_ms > 0)
timeEndPeriod(res_ms);
#endif
}
void mp_sleep_us(int64_t us)
{
if (us < 0)
@ -34,7 +61,9 @@ void mp_sleep_us(int64_t us)
// it may take some time until it actually starts to run again
if (us < 1000)
us = 1000;
int hrt = mp_start_hires_timers(us / 1000);
Sleep(us / 1000);
mp_end_hires_timers(hrt);
}
uint64_t mp_raw_time_us(void)
@ -52,7 +81,37 @@ uint64_t mp_raw_time_us(void)
void mp_raw_time_init(void)
{
QueryPerformanceFrequency(&perf_freq);
#if !HAVE_UWP
timeBeginPeriod(1); // request 1ms timer resolution
// allow (undocumented) control of all the High Res Timers parameters,
// for easier experimentation and diagnostic of bug reports.
const char *v;
// 1..1000 ms max timetout for hires (used in "perwait" mode)
if ((v = getenv("MPV_HRT_MAX"))) {
int hmax = atoi(v);
if (hmax >= 1 && hmax <= 1000)
hires_max = hmax;
}
// 1..15 ms hires resolution (not used in "never" mode)
if ((v = getenv("MPV_HRT_RES"))) {
int res = atoi(v);
if (res >= 1 && res <= 15)
hires_res = res;
}
// "always"/"never"/"perwait" (or "auto" - same as unset)
if (!(v = getenv("MPV_HRT")) || !strcmp(v, "auto"))
v = IsWindows10OrGreater() ? "perwait" : "always";
if (!strcmp(v, "perwait")) {
// no-op, already per-wait
} else if (!strcmp(v, "never")) {
hires_max = 0;
} else { // "always" or unknown value
hires_max = 0;
timeBeginPeriod(hires_res);
}
#endif
}

View File

@ -37,6 +37,14 @@ uint64_t mp_raw_time_us(void);
// Sleep in microseconds.
void mp_sleep_us(int64_t us);
#ifdef _WIN32
// returns: timer resolution in ms if needed and started successfully, else 0
int mp_start_hires_timers(int wait_ms);
// call unconditionally with the return value of mp_start_hires_timers
void mp_end_hires_timers(int resolution_ms);
#endif /* _WIN32 */
#define MP_START_TIME 10000000
// Duration of a second in mpv time.

View File

@ -22,6 +22,8 @@
#include <sys/time.h>
#include <assert.h>
#include "osdep/timer.h" // mp_{start,end}_hires_timers
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
{
BOOL pending;
@ -78,11 +80,13 @@ static int cond_wait(pthread_cond_t *restrict cond,
DWORD ms)
{
BOOL res;
int hrt = mp_start_hires_timers(ms);
if (mutex->use_cs) {
res = SleepConditionVariableCS(cond, &mutex->lock.cs, ms);
} else {
res = SleepConditionVariableSRW(cond, &mutex->lock.srw, ms, 0);
}
mp_end_hires_timers(hrt);
return res ? 0 : ETIMEDOUT;
}