Time scaling works (num pad +/- or --time_scalar=X).

This commit is contained in:
Ben Vanik 2015-05-27 19:04:47 -07:00
parent 0b75d409d2
commit db3d8fdc87
4 changed files with 98 additions and 15 deletions

View File

@ -9,13 +9,43 @@
#include "xenia/base/clock.h" #include "xenia/base/clock.h"
#include <algorithm>
#include "xenia/base/assert.h"
#include "xenia/base/platform.h" #include "xenia/base/platform.h"
namespace xe { namespace xe {
// Time scalar applied to all time operations.
double guest_time_scalar_ = 1.0; 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();
// Base FILETIME of the guest system from app start.
uint64_t guest_system_time_base_ = Clock::QueryHostSystemTime(); uint64_t guest_system_time_base_ = Clock::QueryHostSystemTime();
// Combined time and frequency scalar (computed by RecomputeGuestTickScalar).
double guest_tick_scalar_ = 1.0;
// 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;
// Last sampled host tick count.
thread_local uint64_t last_host_tick_count_ = Clock::QueryHostTickCount();
void RecomputeGuestTickScalar() {
guest_tick_scalar_ = (guest_tick_frequency_ * guest_time_scalar_) /
double(Clock::host_tick_frequency());
}
void 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_;
}
uint64_t Clock::host_tick_frequency() { uint64_t Clock::host_tick_frequency() {
static LARGE_INTEGER frequency = {0}; static LARGE_INTEGER frequency = {0};
@ -46,12 +76,14 @@ double Clock::guest_time_scalar() { return guest_time_scalar_; }
void Clock::set_guest_time_scalar(double scalar) { void Clock::set_guest_time_scalar(double scalar) {
guest_time_scalar_ = scalar; guest_time_scalar_ = scalar;
RecomputeGuestTickScalar();
} }
uint64_t Clock::guest_tick_frequency() { return guest_tick_frequency_; } uint64_t Clock::guest_tick_frequency() { return guest_tick_frequency_; }
void Clock::set_guest_tick_frequency(uint64_t frequency) { void Clock::set_guest_tick_frequency(uint64_t frequency) {
guest_tick_frequency_ = frequency; guest_tick_frequency_ = frequency;
RecomputeGuestTickScalar();
} }
uint64_t Clock::guest_system_time_base() { return guest_system_time_base_; } uint64_t Clock::guest_system_time_base() { return guest_system_time_base_; }
@ -61,36 +93,56 @@ void Clock::set_guest_system_time_base(uint64_t time_base) {
} }
uint64_t Clock::QueryGuestTickCount() { uint64_t Clock::QueryGuestTickCount() {
// TODO(benvanik): adjust. UpdateGuestClock();
return QueryHostTickCount(); return guest_tick_count_;
} }
uint64_t Clock::QueryGuestSystemTime() { uint64_t Clock::QueryGuestSystemTime() {
// TODO(benvanik): adjust. UpdateGuestClock();
return QueryHostSystemTime(); return guest_system_time_base_ + guest_time_filetime_;
} }
uint32_t Clock::QueryGuestUptimeMillis() { uint32_t Clock::QueryGuestUptimeMillis() {
// TODO(benvanik): adjust. UpdateGuestClock();
return QueryHostUptimeMillis(); 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;
} }
uint32_t Clock::ScaleGuestDurationMillis(uint32_t guest_ms) { uint32_t Clock::ScaleGuestDurationMillis(uint32_t guest_ms) {
// TODO(benvanik): adjust. if (guest_ms == UINT_MAX) {
// if -1, return -1 return UINT_MAX;
// if 0, return 0 } else if (!guest_ms) {
return 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)));
} }
int64_t Clock::ScaleGuestDurationFileTime(int64_t guest_file_time) { int64_t Clock::ScaleGuestDurationFileTime(int64_t guest_file_time) {
// TODO(benvanik): adjust.
// negative = relative times // negative = relative times
// positive = absolute times // positive = absolute times
return guest_file_time; // TODO(benvanik): support absolute times.
assert_true(guest_file_time <= 0);
if (!guest_file_time) {
return 0;
}
uint64_t scaled_file_time =
uint64_t(uint64_t(guest_file_time) * guest_time_scalar_);
// TODO(benvanik): check for overflow?
return scaled_file_time;
} }
void Clock::ScaleGuestDurationTimeval(long* tv_sec, long* tv_usec) { void Clock::ScaleGuestDurationTimeval(long* tv_sec, long* tv_usec) {
// TODO(benvanik): adjust. 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 overflow_sec = scaled_usec / 1000000;
scaled_usec -= overflow_sec * 1000000;
scaled_sec += overflow_sec;
}
*tv_sec = long(scaled_sec);
*tv_usec = long(scaled_usec);
} }
} // namespace xe } // namespace xe

View File

@ -72,7 +72,7 @@ X_STATUS Emulator::Setup() {
// Initialize clock. // Initialize clock.
// 360 uses a 50MHz clock. // 360 uses a 50MHz clock.
// Clock::set_guest_tick_frequency(50000000); Clock::set_guest_tick_frequency(50000000);
// We could reset this with save state data/constant value to help replays. // We could reset this with save state data/constant value to help replays.
Clock::set_guest_system_time_base(Clock::QueryHostSystemTime()); Clock::set_guest_system_time_base(Clock::QueryHostSystemTime());
// This can be adjusted dynamically, as well. // This can be adjusted dynamically, as well.

View File

@ -9,6 +9,7 @@
#include "xenia/ui/main_window.h" #include "xenia/ui/main_window.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/threading.h" #include "xenia/base/threading.h"
#include "xenia/gpu/graphics_system.h" #include "xenia/gpu/graphics_system.h"
@ -18,8 +19,10 @@
namespace xe { namespace xe {
namespace ui { namespace ui {
const std::wstring kBaseTitle = L"xenia";
MainWindow::MainWindow(Emulator* emulator) MainWindow::MainWindow(Emulator* emulator)
: PlatformWindow(L"xenia"), : PlatformWindow(kBaseTitle),
emulator_(emulator), emulator_(emulator),
main_menu_(MenuItem::Type::kNormal) {} main_menu_(MenuItem::Type::kNormal) {}
@ -48,6 +51,7 @@ bool MainWindow::Initialize() {
return false; return false;
} }
Resize(1280, 720); Resize(1280, 720);
UpdateTitle();
on_key_down.AddListener([this](KeyEvent& e) { on_key_down.AddListener([this](KeyEvent& e) {
bool handled = true; bool handled = true;
switch (e.key_code()) { switch (e.key_code()) {
@ -59,6 +63,21 @@ bool MainWindow::Initialize() {
emulator()->graphics_system()->ClearCaches(); emulator()->graphics_system()->ClearCaches();
break; break;
} }
case 0x6D: { // numpad minus
Clock::set_guest_time_scalar(Clock::guest_time_scalar() / 2.0);
UpdateTitle();
break;
}
case 0x6B: { // numpad plus
Clock::set_guest_time_scalar(Clock::guest_time_scalar() * 2.0);
UpdateTitle();
break;
}
case 0x0D: { // numpad enter
Clock::set_guest_time_scalar(1.0);
UpdateTitle();
break;
}
default: { default: {
handled = false; handled = false;
break; break;
@ -83,6 +102,16 @@ bool MainWindow::Initialize() {
return true; return true;
} }
void MainWindow::UpdateTitle() {
std::wstring title(kBaseTitle);
if (Clock::guest_time_scalar() != 1.0) {
title += L" (@";
title += xe::to_wstring(std::to_string(Clock::guest_time_scalar()));
title += L"x)";
}
set_title(title);
}
void MainWindow::OnClose() { void MainWindow::OnClose() {
loop_.Quit(); loop_.Quit();

View File

@ -44,6 +44,8 @@ class MainWindow : public PlatformWindow {
private: private:
bool Initialize(); bool Initialize();
void UpdateTitle();
void OnClose() override; void OnClose() override;
void OnCommand(int id) override; void OnCommand(int id) override;