245 lines
7.8 KiB
C++
245 lines
7.8 KiB
C++
/**
|
|
******************************************************************************
|
|
* 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/clock.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <mutex>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/math.h"
|
|
|
|
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_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.
|
|
// 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.
|
|
uint64_t last_guest_tick_count_ = 0;
|
|
// Last sampled host tick count.
|
|
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() {
|
|
// Create a rational number with numerator (first) and denominator (second)
|
|
auto frac =
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
// 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();
|
|
|
|
if (cvars::clock_no_scaling) {
|
|
// Nothing to update, calculate on the fly
|
|
return host_tick_count * guest_tick_ratio_.first / guest_tick_ratio_.second;
|
|
}
|
|
|
|
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() {
|
|
if (cvars::clock_no_scaling) {
|
|
return Clock::QueryHostSystemTime() - guest_system_time_base_;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
if (cvars::clock_no_scaling) {
|
|
return;
|
|
}
|
|
|
|
guest_time_scalar_ = scalar;
|
|
RecomputeGuestTickScalar();
|
|
}
|
|
|
|
std::pair<uint64_t, uint64_t> Clock::guest_tick_ratio() {
|
|
std::lock_guard<std::mutex> lock(tick_mutex_);
|
|
return guest_tick_ratio_;
|
|
}
|
|
|
|
uint64_t Clock::guest_tick_frequency() { return guest_tick_frequency_; }
|
|
|
|
void Clock::set_guest_tick_frequency(uint64_t frequency) {
|
|
guest_tick_frequency_ = frequency;
|
|
RecomputeGuestTickScalar();
|
|
}
|
|
|
|
uint64_t Clock::guest_system_time_base() { return guest_system_time_base_; }
|
|
|
|
void Clock::set_guest_system_time_base(uint64_t time_base) {
|
|
guest_system_time_base_ = time_base;
|
|
}
|
|
|
|
uint64_t Clock::QueryGuestTickCount() {
|
|
auto guest_tick_count = UpdateGuestClock();
|
|
return guest_tick_count;
|
|
}
|
|
|
|
uint64_t Clock::QueryGuestSystemTime() {
|
|
if (cvars::clock_no_scaling) {
|
|
return Clock::QueryHostSystemTime();
|
|
}
|
|
|
|
auto guest_system_time_offset = QueryGuestSystemTimeOffset();
|
|
return guest_system_time_base_ + guest_system_time_offset;
|
|
}
|
|
|
|
uint32_t Clock::QueryGuestUptimeMillis() {
|
|
return static_cast<uint32_t>(
|
|
std::min<uint64_t>(QueryGuestSystemTimeOffset() / 10000,
|
|
std::numeric_limits<uint32_t>::max()));
|
|
}
|
|
|
|
void Clock::SetGuestSystemTime(uint64_t system_time) {
|
|
if (cvars::clock_no_scaling) {
|
|
// Time is fixed to host time.
|
|
return;
|
|
}
|
|
|
|
// 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 (cvars::clock_no_scaling) {
|
|
return guest_ms;
|
|
}
|
|
|
|
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 = 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) {
|
|
if (cvars::clock_no_scaling) {
|
|
return static_cast<uint64_t>(guest_file_time);
|
|
}
|
|
|
|
if (!guest_file_time) {
|
|
return 0;
|
|
} else if (guest_file_time > 0) {
|
|
// Absolute time.
|
|
uint64_t guest_time = Clock::QueryGuestSystemTime();
|
|
int64_t relative_time = guest_file_time - static_cast<int64_t>(guest_time);
|
|
int64_t scaled_time =
|
|
static_cast<int64_t>(relative_time * guest_time_scalar_);
|
|
return static_cast<int64_t>(guest_time) + scaled_time;
|
|
} else {
|
|
// Relative time.
|
|
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) {
|
|
if (cvars::clock_no_scaling) {
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
*tv_sec = int32_t(scaled_sec);
|
|
*tv_usec = int32_t(scaled_usec);
|
|
}
|
|
|
|
} // namespace xe
|