Open StrikerX3 opened 4 years ago
Historically, there's been done more research on the topic; Here a few similar reports from around the world:
http://www.geisswerks.com/ryan/FAQS/timing.html http://www.virtualdub.org/blog/pivot/entry.php?id=272 https://omeg.pl/blog/2011/11/on-winapi-timers-and-their-resolution/
What it boils down to, is that timeSetPeriod(1)+timeSetEvent() and busy-wait loops (using QPF+QPC) are most reliable, combining them together makes for the most accurate and precise timing
Also related : https://en.wikipedia.org/wiki/Accuracy_and_precision#ISO_definition_(ISO_5725) https://stackoverflow.com/a/29183085/12170 https://stackoverflow.com/questions/7685762/windows-7-timing-functions-how-to-use-getsystemtimeadjustment-correctly http://blog.nuclex-games.com/2012/04/perfectly-accurate-game-timing/ https://redream.io/posts/improving-audio-video-synchronization-multi-sync https://www.gamasutra.com/view/feature/171774/getting_high_precision_timing_on_.php?print=1
EDIT Oh, and : https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/timer-accuracy
The method currently in use by timing-sensitive threads such as the i8254 timer loop (which needs to trigger once every millisecond) is not accurate enough on Windows. In both computers I have access to, the loop does trigger 1000 times per second as expected, but it fires two events every ~2ms instead of one every 1ms.
Windows offers the following timer and timing-related APIs:
timeBeginPeriod(1)
succeeds. (It should succeed on basically any modern computer using at least Windows 7 which is what StrikeBox targets.)timeSetEvent
API. My tests argue otherwise.Here's a snapshot of 100 milliseconds of execution using each technique. Time is measured in microseconds with
QueryPerformanceCounter
. Each dot represents an event fired by one of the methods at a given moment. The system was under light load at the time of the test, which should provide a realistic environment similar to the expected usage of the emulator. The code was compiled in 64-bit Release mode.The graph describes exactly what the issue is with
std::this_thread::sleep_until
: it doesn't trigger frequently enough.QPC triggers almost perfectly in sync with the expected tick rate, but it costs 100% of a CPU core and is still subject to thread preemption (as seen in the last tick).
CreateWaitableTimer
/SetWaitableTimer
andCreateTimerQueue
/CreateTimerQueueTimer
are unsuited to the task. They missed a lot of ticks and drifted away from the desired tick rate.timeSetEvent
is the best option out of all these. It's not perfect either, but we can't expect perfect accuracy from a non-realtime operating system. However, it is much better than the current technique.I haven't tested this on Linux yet. In any case, if the
std::this_thread::sleep_until
method is not accurate enough,timer_create
seems to be the solution.