diff --git a/src/xenia/base/clock.cc b/src/xenia/base/clock.cc index b79e76ae9..5eeec31a4 100644 --- a/src/xenia/base/clock.cc +++ b/src/xenia/base/clock.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2015 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. * ****************************************************************************** */ @@ -10,9 +10,11 @@ #include "xenia/base/clock.h" #include -#include +#include +#include #include "xenia/base/assert.h" +#include "xenia/base/math.h" namespace xe { @@ -22,29 +24,70 @@ double guest_time_scalar_ = 1.0; uint64_t guest_tick_frequency_ = Clock::host_tick_frequency(); // Base FILETIME of the guest system from app start. uint64_t guest_system_time_base_ = Clock::QueryHostSystemTime(); -// Combined time and frequency scalar (computed by RecomputeGuestTickScalar). -double guest_tick_scalar_ = 1.0; +// Combined time and frequency ratio between host and guest. +// Split in numerator (first) and denominator (second). +// Computed by RecomputeGuestTickScalar. +std::pair guest_tick_ratio_ = std::make_pair(1, 1); + // Native guest ticks. -thread_local uint64_t guest_tick_count_ = 0; -// 100ns ticks, relative to guest_system_time_base_. -thread_local uint64_t guest_time_filetime_ = 0; +uint64_t last_guest_tick_count_ = 0; // Last sampled host tick count. -thread_local uint64_t last_host_tick_count_ = Clock::QueryHostTickCount(); +uint64_t last_host_tick_count_ = Clock::QueryHostTickCount(); +// Mutex to ensure last_host_tick_count_ and last_guest_tick_count_ are in sync +std::mutex tick_mutex_; void RecomputeGuestTickScalar() { - guest_tick_scalar_ = (guest_tick_frequency_ * guest_time_scalar_) / - static_cast(Clock::host_tick_frequency()); + // Create a rational number with numerator (first) and denominator (second) + auto frac = + std::make_pair(guest_tick_frequency_, Clock::host_tick_frequency()); + // 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) { + frac.first *= static_cast(guest_time_scalar_ * 10.0); + frac.second *= 10; + } else { + frac.first *= 10; + frac.second *= static_cast(10.0 / guest_time_scalar_); + } + // Keep this a rational calculation and reduce the fraction + reduce_fraction(frac); + + std::lock_guard lock(tick_mutex_); + guest_tick_ratio_ = frac; } -void UpdateGuestClock() { +// Update the guest timer for all threads. +// Return a copy of the value so locking is reduced. +uint64_t UpdateGuestClock() { uint64_t host_tick_count = Clock::QueryHostTickCount(); - uint64_t host_tick_delta = host_tick_count > last_host_tick_count_ - ? host_tick_count - last_host_tick_count_ - : 0; - last_host_tick_count_ = host_tick_count; - uint64_t guest_tick_delta = uint64_t(host_tick_delta * guest_tick_scalar_); - guest_tick_count_ += guest_tick_delta; - guest_time_filetime_ += (guest_tick_delta * 10000000) / guest_tick_frequency_; + + std::unique_lock lock(tick_mutex_, std::defer_lock); + if (lock.try_lock()) { + // Translate host tick count to guest tick count. + uint64_t host_tick_delta = host_tick_count > last_host_tick_count_ + ? host_tick_count - last_host_tick_count_ + : 0; + last_host_tick_count_ = host_tick_count; + uint64_t guest_tick_delta = + host_tick_delta * guest_tick_ratio_.first / guest_tick_ratio_.second; + last_guest_tick_count_ += guest_tick_delta; + return last_guest_tick_count_; + } else { + // Wait until another thread has finished updating the clock. + lock.lock(); + return last_guest_tick_count_; + } +} + +// Offset of the current guest system file time relative to the guest base time. +inline uint64_t QueryGuestSystemTimeOffset() { + auto guest_tick_count = UpdateGuestClock(); + + uint64_t numerator = 10000000; // 100ns/10MHz resolution + uint64_t denominator = guest_tick_frequency_; + reduce_fraction(numerator, denominator); + + return guest_tick_count * numerator / denominator; } double Clock::guest_time_scalar() { return guest_time_scalar_; } @@ -68,40 +111,45 @@ void Clock::set_guest_system_time_base(uint64_t time_base) { } uint64_t Clock::QueryGuestTickCount() { - UpdateGuestClock(); - return guest_tick_count_; + auto guest_tick_count = UpdateGuestClock(); + return guest_tick_count; } uint64_t Clock::QueryGuestSystemTime() { - UpdateGuestClock(); - return guest_system_time_base_ + guest_time_filetime_; + auto guest_system_time_offset = QueryGuestSystemTimeOffset(); + return guest_system_time_base_ + guest_system_time_offset; } uint32_t Clock::QueryGuestUptimeMillis() { - UpdateGuestClock(); - uint64_t uptime_millis = guest_tick_count_ / (guest_tick_frequency_ / 1000); - uint32_t result = uint32_t(std::min(uptime_millis, uint64_t(UINT_MAX))); - return result; + return static_cast( + std::min(QueryGuestSystemTimeOffset() / 10000, + std::numeric_limits::max())); } void Clock::SetGuestTickCount(uint64_t tick_count) { + std::lock_guard lock(tick_mutex_); + last_host_tick_count_ = Clock::QueryHostTickCount(); - guest_tick_count_ = tick_count; + last_guest_tick_count_ = tick_count; } void Clock::SetGuestSystemTime(uint64_t system_time) { - last_host_tick_count_ = Clock::QueryHostTickCount(); - guest_time_filetime_ = system_time - guest_system_time_base_; + // Query the filetime offset to calculate a new base time. + auto guest_system_time_offset = QueryGuestSystemTimeOffset(); + guest_system_time_base_ = system_time - guest_system_time_offset; } uint32_t Clock::ScaleGuestDurationMillis(uint32_t guest_ms) { - if (guest_ms == UINT_MAX) { - return UINT_MAX; + constexpr uint64_t max = std::numeric_limits::max(); + + if (guest_ms >= max) { + return max; } else if (!guest_ms) { return 0; } - uint64_t scaled_ms = uint64_t(uint64_t(guest_ms) * guest_time_scalar_); - return uint32_t(std::min(scaled_ms, uint64_t(UINT_MAX))); + uint64_t scaled_ms = static_cast( + (static_cast(guest_ms) * guest_time_scalar_)); + return static_cast(std::min(scaled_ms, max)); } int64_t Clock::ScaleGuestDurationFileTime(int64_t guest_file_time) { @@ -116,17 +164,19 @@ int64_t Clock::ScaleGuestDurationFileTime(int64_t guest_file_time) { return static_cast(guest_time) + scaled_time; } else { // Relative time. - uint64_t scaled_file_time = - uint64_t(uint64_t(guest_file_time) * guest_time_scalar_); + uint64_t scaled_file_time = static_cast( + (static_cast(guest_file_time) * guest_time_scalar_)); // TODO(benvanik): check for overflow? return scaled_file_time; } } void Clock::ScaleGuestDurationTimeval(int32_t* tv_sec, int32_t* tv_usec) { - uint64_t scaled_sec = uint64_t(uint64_t(*tv_sec) * guest_tick_scalar_); - uint64_t scaled_usec = uint64_t(uint64_t(*tv_usec) * guest_time_scalar_); - if (scaled_usec > UINT_MAX) { + uint64_t scaled_sec = static_cast(static_cast(*tv_sec) * + guest_time_scalar_); + uint64_t scaled_usec = static_cast(static_cast(*tv_usec) * + guest_time_scalar_); + if (scaled_usec > std::numeric_limits::max()) { uint64_t overflow_sec = scaled_usec / 1000000; scaled_usec -= overflow_sec * 1000000; scaled_sec += overflow_sec; diff --git a/src/xenia/base/clock_win.cc b/src/xenia/base/clock_win.cc index aa028c43b..7d33d0c93 100644 --- a/src/xenia/base/clock_win.cc +++ b/src/xenia/base/clock_win.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2015 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,10 +14,8 @@ namespace xe { uint64_t Clock::host_tick_frequency() { - static LARGE_INTEGER frequency = {{0}}; - if (!frequency.QuadPart) { - QueryPerformanceFrequency(&frequency); - } + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); return frequency.QuadPart; } diff --git a/src/xenia/base/math.h b/src/xenia/base/math.h index 8852636d2..02cb47074 100644 --- a/src/xenia/base/math.h +++ b/src/xenia/base/math.h @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2014 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. * ****************************************************************************** */ @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "xenia/base/platform.h" @@ -59,6 +60,34 @@ T next_pow2(T value) { return value; } +#if __cpp_lib_gcd_lcm +template +inline constexpr T greatest_common_divisor(T a, T b) { + return std::gcd(a, b); +} +#else +template +constexpr T greatest_common_divisor(T a, T b) { + // Use the Euclid algorithm to calculate the greatest common divisor + while (b) { + a = std::exchange(b, a % b); + } + return a; +} +#endif + +template +inline constexpr void reduce_fraction(T& numerator, T& denominator) { + auto gcd = greatest_common_divisor(numerator, denominator); + numerator /= gcd; + denominator /= gcd; +} + +template +inline constexpr void reduce_fraction(std::pair& fraction) { + reduce_fraction(fraction.first, fraction.second); +} + constexpr uint32_t make_bitmask(uint32_t a, uint32_t b) { return (static_cast(-1) >> (31 - b)) & ~((1u << a) - 1); }