summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Linn <jl@conductive.de>2019-11-20 00:28:08 +0100
committerRick Gibbed <rick@gibbed.us>2019-12-01 17:11:58 -0600
commitd6ce72ddc90a18b5b0944b9be92bc8004d1ebc96 (patch)
treea10bf070fc01214c1a99ceca45b55fdad844a4e1
parent6a3a56b3b95f291c083f23bf5dba398723e202dc (diff)
[Base] Clock reworked.
- Time progression is now equal and in sync on all threads. - Floating point imprecisions do not interfere with timing.
-rw-r--r--src/xenia/base/clock.cc126
-rw-r--r--src/xenia/base/clock_win.cc8
-rw-r--r--src/xenia/base/math.h31
3 files changed, 121 insertions, 44 deletions
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 <algorithm>
-#include <climits>
+#include <limits>
+#include <mutex>
#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<uint64_t, uint64_t> 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<double>(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<uint64_t>(guest_time_scalar_ * 10.0);
+ frac.second *= 10;
+ } else {
+ frac.first *= 10;
+ frac.second *= static_cast<uint64_t>(10.0 / guest_time_scalar_);
+ }
+ // Keep this a rational calculation and reduce the fraction
+ reduce_fraction(frac);
+
+ std::lock_guard<std::mutex> 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<std::mutex> 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<uint32_t>(
+ std::min<uint64_t>(QueryGuestSystemTimeOffset() / 10000,
+ std::numeric_limits<uint32_t>::max()));
}
void Clock::SetGuestTickCount(uint64_t tick_count) {
+ std::lock_guard<std::mutex> 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<uint32_t>::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<uint64_t>(
+ (static_cast<uint64_t>(guest_ms) * guest_time_scalar_));
+ return static_cast<uint32_t>(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<int64_t>(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<uint64_t>(
+ (static_cast<uint64_t>(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<uint64_t>(static_cast<uint64_t>(*tv_sec) *
+ guest_time_scalar_);
+ uint64_t scaled_usec = static_cast<uint64_t>(static_cast<uint64_t>(*tv_usec) *
+ guest_time_scalar_);
+ if (scaled_usec > std::numeric_limits<uint32_t>::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 <cstdint>
#include <cstring>
#include <limits>
+#include <numeric>
#include <type_traits>
#include "xenia/base/platform.h"
@@ -59,6 +60,34 @@ T next_pow2(T value) {
return value;
}
+#if __cpp_lib_gcd_lcm
+template <typename T>
+inline constexpr T greatest_common_divisor(T a, T b) {
+ return std::gcd(a, b);
+}
+#else
+template <typename T>
+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 <typename T>
+inline constexpr void reduce_fraction(T& numerator, T& denominator) {
+ auto gcd = greatest_common_divisor(numerator, denominator);
+ numerator /= gcd;
+ denominator /= gcd;
+}
+
+template <typename T>
+inline constexpr void reduce_fraction(std::pair<T, T>& fraction) {
+ reduce_fraction<T>(fraction.first, fraction.second);
+}
+
constexpr uint32_t make_bitmask(uint32_t a, uint32_t b) {
return (static_cast<uint32_t>(-1) >> (31 - b)) & ~((1u << a) - 1);
}