[Base] Optional raw Clock source.
New cvar clock_source_raw allows to use the cpu cycle counter itself as an alternative time source, if system timing resolution is to low and causes problems.
This commit is contained in:
parent
f88d46cead
commit
15d422d988
|
@ -20,13 +20,17 @@ DEFINE_bool(clock_no_scaling, false,
|
|||
"Disable scaling code. Time management and locking is bypassed. "
|
||||
"Guest system time is directly pulled from host.",
|
||||
"CPU");
|
||||
DEFINE_bool(clock_source_raw, false,
|
||||
"Use the RDTSC instruction as the time source. "
|
||||
"Host CPU must support invariant TSC. ",
|
||||
"CPU");
|
||||
|
||||
namespace xe {
|
||||
|
||||
// Time scalar applied to all time operations.
|
||||
double guest_time_scalar_ = 1.0;
|
||||
// Tick frequency of guest.
|
||||
uint64_t guest_tick_frequency_ = Clock::host_tick_frequency();
|
||||
uint64_t guest_tick_frequency_ = Clock::host_tick_frequency_platform();
|
||||
// Base FILETIME of the guest system from app start.
|
||||
uint64_t guest_system_time_base_ = Clock::QueryHostSystemTime();
|
||||
// Combined time and frequency ratio between host and guest.
|
||||
|
@ -44,7 +48,7 @@ std::mutex tick_mutex_;
|
|||
void RecomputeGuestTickScalar() {
|
||||
// Create a rational number with numerator (first) and denominator (second)
|
||||
auto frac =
|
||||
std::make_pair(guest_tick_frequency_, Clock::host_tick_frequency());
|
||||
std::make_pair(guest_tick_frequency_, Clock::QueryHostTickFrequency());
|
||||
// Doing it this way ensures we don't mess up our frequency scaling and
|
||||
// precisely controls the precision the guest_time_scalar_ can have.
|
||||
if (guest_time_scalar_ > 1.0) {
|
||||
|
@ -104,6 +108,21 @@ inline uint64_t QueryGuestSystemTimeOffset() {
|
|||
return guest_tick_count * numerator / denominator;
|
||||
}
|
||||
|
||||
uint64_t Clock::QueryHostTickFrequency() {
|
||||
if (cvars::clock_source_raw) {
|
||||
return host_tick_frequency_raw();
|
||||
} else {
|
||||
return host_tick_frequency_platform();
|
||||
}
|
||||
}
|
||||
uint64_t Clock::QueryHostTickCount() {
|
||||
if (cvars::clock_source_raw) {
|
||||
return host_tick_count_raw();
|
||||
} else {
|
||||
return host_tick_count_platform();
|
||||
}
|
||||
}
|
||||
|
||||
double Clock::guest_time_scalar() { return guest_time_scalar_; }
|
||||
|
||||
void Clock::set_guest_time_scalar(double scalar) {
|
||||
|
|
|
@ -15,13 +15,22 @@
|
|||
#include "xenia/base/cvar.h"
|
||||
|
||||
DECLARE_bool(clock_no_scaling);
|
||||
DECLARE_bool(clock_source_raw);
|
||||
|
||||
namespace xe {
|
||||
|
||||
class Clock {
|
||||
public:
|
||||
// Host ticks-per-second.
|
||||
static uint64_t host_tick_frequency();
|
||||
// Host ticks-per-second. Generally QueryHostTickFrequency should be used.
|
||||
// Either from platform suplied time source or from hardware directly.
|
||||
static uint64_t host_tick_frequency_platform();
|
||||
static uint64_t host_tick_frequency_raw();
|
||||
// Host tick count. Generally QueryHostTickCount() should be used.
|
||||
static uint64_t host_tick_count_platform();
|
||||
static uint64_t host_tick_count_raw();
|
||||
|
||||
// Queries the host tick frequency.
|
||||
static uint64_t QueryHostTickFrequency();
|
||||
// Queries the current host tick count.
|
||||
static uint64_t QueryHostTickCount();
|
||||
// Host time, in FILETIME format.
|
||||
|
@ -43,6 +52,7 @@ class Clock {
|
|||
// Sets the guest time base, used for computing the system time.
|
||||
// By default this is the current system time.
|
||||
static void set_guest_system_time_base(uint64_t time_base);
|
||||
|
||||
// Queries the current guest tick count, accounting for frequency adjustment
|
||||
// and scaling.
|
||||
static uint64_t QueryGuestTickCount();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2017 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2019 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -14,14 +14,14 @@
|
|||
|
||||
namespace xe {
|
||||
|
||||
uint64_t Clock::host_tick_frequency() {
|
||||
uint64_t Clock::host_tick_frequency_platform() {
|
||||
timespec res;
|
||||
clock_getres(CLOCK_MONOTONIC_RAW, &res);
|
||||
|
||||
return uint64_t(res.tv_sec) + uint64_t(res.tv_nsec) * 1000000000ull;
|
||||
}
|
||||
|
||||
uint64_t Clock::QueryHostTickCount() {
|
||||
uint64_t Clock::host_tick_count_platform() {
|
||||
timespec res;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &res);
|
||||
|
||||
|
@ -40,7 +40,7 @@ uint64_t Clock::QueryHostSystemTime() {
|
|||
}
|
||||
|
||||
uint64_t Clock::QueryHostUptimeMillis() {
|
||||
return QueryHostTickCount() / (host_tick_frequency() / 1000);
|
||||
return host_tick_count_platform() * 1000 / host_tick_frequency_platform();
|
||||
}
|
||||
|
||||
} // namespace xe
|
|
@ -13,13 +13,13 @@
|
|||
|
||||
namespace xe {
|
||||
|
||||
uint64_t Clock::host_tick_frequency() {
|
||||
uint64_t Clock::host_tick_frequency_platform() {
|
||||
LARGE_INTEGER frequency;
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
return frequency.QuadPart;
|
||||
}
|
||||
|
||||
uint64_t Clock::QueryHostTickCount() {
|
||||
uint64_t Clock::host_tick_count_platform() {
|
||||
LARGE_INTEGER counter;
|
||||
uint64_t time = 0;
|
||||
if (QueryPerformanceCounter(&counter)) {
|
||||
|
@ -35,7 +35,7 @@ uint64_t Clock::QueryHostSystemTime() {
|
|||
}
|
||||
|
||||
uint64_t Clock::QueryHostUptimeMillis() {
|
||||
return QueryHostTickCount() / (host_tick_frequency() / 1000);
|
||||
return host_tick_count_platform() * 1000 / host_tick_frequency_platform();
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2019 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
#if XE_ARCH_AMD64
|
||||
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
// Wrap all these different cpu compiler intrinsics.
|
||||
// So no inline assembler here and the compiler will remove the clutter.
|
||||
#if XE_COMPILER_MSVC
|
||||
#define xe_cpu_cpuid(level, eax, ebx, ecx, edx) \
|
||||
{ \
|
||||
int __xe_cpuid_registers_[4]; \
|
||||
__cpuid(__xe_cpuid_registers_, (level)); \
|
||||
(eax) = static_cast<uint32_t>(__xe_cpuid_registers_[0]); \
|
||||
(ebx) = static_cast<uint32_t>(__xe_cpuid_registers_[1]); \
|
||||
(ecx) = static_cast<uint32_t>(__xe_cpuid_registers_[2]); \
|
||||
(edx) = static_cast<uint32_t>(__xe_cpuid_registers_[3]); \
|
||||
}
|
||||
#define xe_cpu_rdtsc() __rdtsc()
|
||||
#elif XE_COMPILER_CLANG || XE_COMPILER_GNUC
|
||||
#include <cpuid.h>
|
||||
#define xe_cpu_cpuid(level, eax, ebx, ecx, edx) \
|
||||
__cpuid((level), (eax), (ebx), (ecx), (edx));
|
||||
#define xe_cpu_rdtsc() __rdtsc()
|
||||
#else
|
||||
#error "No cpu instruction wrappers for current compiler implemented."
|
||||
#endif
|
||||
|
||||
#define CLOCK_FATAL(msg) \
|
||||
xe::FatalError( \
|
||||
"The raw clock source is not supported on your CPU. \n" \
|
||||
"%s \n" \
|
||||
"Set the cvar 'clock_source_raw' to 'false'.", \
|
||||
(msg));
|
||||
|
||||
namespace xe {
|
||||
// Getting the TSC frequency can be a bit tricky. This method here only works on
|
||||
// Intel as it seems. There is no easy way to get the frequency outside of ring0
|
||||
// on AMD, so we fail gracefully if not possible.
|
||||
uint64_t Clock::host_tick_frequency_raw() {
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
|
||||
// 00H Get max supported cpuid level.
|
||||
xe_cpu_cpuid(0x0, eax, ebx, ecx, edx);
|
||||
auto max_cpuid = eax;
|
||||
// 80000000H Get max extended cpuid level
|
||||
xe_cpu_cpuid(0x80000000, eax, ebx, ecx, edx);
|
||||
auto max_cpuid_ex = eax;
|
||||
|
||||
// 80000007H Get extended power feature info
|
||||
if (max_cpuid_ex >= 0x80000007) {
|
||||
xe_cpu_cpuid(0x80000007, eax, ebx, ecx, edx);
|
||||
// Invariant TSC bit at position 8
|
||||
auto tsc_invariant = edx & (1 << 8);
|
||||
// If the TSC is not invariant it will change its frequency with power
|
||||
// states and across cores.
|
||||
if (!tsc_invariant) {
|
||||
CLOCK_FATAL("The CPU has no invariant TSC.");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
CLOCK_FATAL("Unclear if the CPU has an invariant TSC.")
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (max_cpuid >= 0x15) {
|
||||
// 15H Get TSC/Crystal ratio and Crystal Hz.
|
||||
xe_cpu_cpuid(0x15, eax, ebx, ecx, edx);
|
||||
uint64_t ratio_num = ebx;
|
||||
uint64_t ratio_den = eax;
|
||||
uint64_t cryst_freq = ecx;
|
||||
// For some CPUs, Crystal frequency is not reported.
|
||||
if (ratio_num && ratio_den && cryst_freq) {
|
||||
// If it is, calculate the TSC frequency
|
||||
auto tsc_freq = cryst_freq * ratio_num / ratio_den;
|
||||
}
|
||||
}
|
||||
|
||||
if (max_cpuid >= 0x16) {
|
||||
// 16H Get CPU base frequency MHz in EAX.
|
||||
xe_cpu_cpuid(0x16, eax, ebx, ecx, edx);
|
||||
uint64_t cpu_base_freq = static_cast<uint64_t>(eax) * 1000000;
|
||||
assert(cpu_base_freq);
|
||||
return cpu_base_freq;
|
||||
}
|
||||
|
||||
CLOCK_FATAL("The clock frequency could not be determined.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t Clock::host_tick_count_raw() { return xe_cpu_rdtsc(); }
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue