Refactoring to enable future time scaling, coordinated clocks, etc.

This commit is contained in:
Ben Vanik 2015-05-26 22:20:46 -07:00
parent 05f2577fb7
commit 8244409501
19 changed files with 213 additions and 57 deletions

View File

@ -25,6 +25,7 @@
<ClCompile Include="src\xenia\apu\xaudio2\xaudio2_audio_driver.cc" />
<ClCompile Include="src\xenia\apu\xaudio2\xaudio2_audio_system.cc" />
<ClCompile Include="src\xenia\base\arena.cc" />
<ClCompile Include="src\xenia\base\clock.cc" />
<ClCompile Include="src\xenia\base\debugging_win.cc" />
<ClCompile Include="src\xenia\base\fs.cc" />
<ClCompile Include="src\xenia\base\fs_win.cc" />
@ -217,6 +218,7 @@
<ClInclude Include="src\xenia\base\assert.h" />
<ClInclude Include="src\xenia\base\atomic.h" />
<ClInclude Include="src\xenia\base\byte_order.h" />
<ClInclude Include="src\xenia\base\clock.h" />
<ClInclude Include="src\xenia\base\debugging.h" />
<ClInclude Include="src\xenia\base\delegate.h" />
<ClInclude Include="src\xenia\base\fs.h" />

View File

@ -706,6 +706,9 @@
<ClCompile Include="src\xenia\apu\audio_decoder.cc">
<Filter>src\xenia\apu</Filter>
</ClCompile>
<ClCompile Include="src\xenia\base\clock.cc">
<Filter>src\xenia\base</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\xenia\emulator.h">
@ -1362,6 +1365,9 @@
<ClInclude Include="src\xenia\base\mutex.h">
<Filter>src\xenia\base</Filter>
</ClInclude>
<ClInclude Include="src\xenia\base\clock.h">
<Filter>src\xenia\base</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="src\xenia\cpu\backend\x64\x64_sequence.inl">

96
src/xenia/base/clock.cc Normal file
View File

@ -0,0 +1,96 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/base/clock.h"
#include "xenia/base/platform.h"
namespace xe {
double guest_time_scalar_ = 1.0;
uint64_t guest_tick_frequency_ = Clock::host_tick_frequency();
uint64_t guest_system_time_base_ = Clock::QueryHostSystemTime();
uint64_t Clock::host_tick_frequency() {
static LARGE_INTEGER frequency = {0};
if (!frequency.QuadPart) {
QueryPerformanceFrequency(&frequency);
}
return frequency.QuadPart;
}
uint64_t Clock::QueryHostTickCount() {
LARGE_INTEGER counter;
uint64_t time = 0;
if (QueryPerformanceCounter(&counter)) {
time = counter.QuadPart;
}
return time;
}
uint64_t Clock::QueryHostSystemTime() {
FILETIME t;
GetSystemTimeAsFileTime(&t);
return (uint64_t(t.dwHighDateTime) << 32) | t.dwLowDateTime;
}
uint32_t Clock::QueryHostUptimeMillis() { return ::GetTickCount(); }
double Clock::guest_time_scalar() { return guest_time_scalar_; }
void Clock::set_guest_time_scalar(double scalar) {
guest_time_scalar_ = scalar;
}
uint64_t Clock::guest_tick_frequency() { return guest_tick_frequency_; }
void Clock::set_guest_tick_frequency(uint64_t frequency) {
guest_tick_frequency_ = frequency;
}
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() {
// TODO(benvanik): adjust.
return QueryHostTickCount();
}
uint64_t Clock::QueryGuestSystemTime() {
// TODO(benvanik): adjust.
return QueryHostSystemTime();
}
uint32_t Clock::QueryGuestUptimeMillis() {
// TODO(benvanik): adjust.
return QueryHostUptimeMillis();
}
uint32_t Clock::ScaleGuestDurationMillis(uint32_t guest_ms) {
// TODO(benvanik): adjust.
// if -1, return -1
// if 0, return 0
return guest_ms;
}
int64_t Clock::ScaleGuestDurationFileTime(int64_t guest_file_time) {
// TODO(benvanik): adjust.
// negative = relative times
// positive = absolute times
return guest_file_time;
}
void Clock::ScaleGuestDurationTimeval(long* tv_sec, long* tv_usec) {
// TODO(benvanik): adjust.
}
} // namespace xe

60
src/xenia/base/clock.h Normal file
View File

@ -0,0 +1,60 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_CLOCK_H_
#define XENIA_BASE_CLOCK_H_
#include <cstdint>
namespace xe {
class Clock {
public:
// Host ticks-per-second.
static uint64_t host_tick_frequency();
// Queries the current host tick count.
static uint64_t QueryHostTickCount();
// Host time, in FILETIME format.
static uint64_t QueryHostSystemTime();
// Queries the milliseconds since the host began.
static uint32_t QueryHostUptimeMillis();
// Guest time scalar.
static double guest_time_scalar();
// Sets the guest time scalar, adjusting tick and wall clock speed.
// Ex: 1x=normal, 2x=double speed, 1/2x=half speed.
static void set_guest_time_scalar(double scalar);
// Guest ticks-per-second.
static uint64_t guest_tick_frequency();
// Sets the guest ticks-per-second.
static void set_guest_tick_frequency(uint64_t frequency);
// Time based used for the guest system time.
static uint64_t guest_system_time_base();
// 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();
// Queries the guest time, in FILETIME format, accounting for scaling.
static uint64_t QueryGuestSystemTime();
// Queries the milliseconds since the guest began, accounting for scaling.
static uint32_t QueryGuestUptimeMillis();
// Scales a time duration in milliseconds, from guest time.
static uint32_t ScaleGuestDurationMillis(uint32_t guest_ms);
// Scales a time duration in 100ns ticks like FILETIME, from guest time.
static int64_t ScaleGuestDurationFileTime(int64_t guest_file_time);
// Scales a time duration represented as a timeval, from guest time.
static void ScaleGuestDurationTimeval(long* tv_sec, long* tv_usec);
};
} // namespace xe
#endif // XENIA_BASE_CLOCK_H_

View File

@ -43,10 +43,6 @@ class Fence {
std::atomic<bool> signaled_;
};
// Gets the current high-performance tick count.
uint64_t ticks();
uint64_t ticks_per_second();
// TODO(benvanik): processor info API.
// Gets a stable thread-specific ID, but may not be. Use for informative

View File

@ -14,23 +14,6 @@
namespace xe {
namespace threading {
uint64_t ticks() {
LARGE_INTEGER counter;
uint64_t time = 0;
if (QueryPerformanceCounter(&counter)) {
time = counter.QuadPart;
}
return time;
}
uint64_t ticks_per_second() {
static LARGE_INTEGER freq = {0};
if (!freq.QuadPart) {
QueryPerformanceFrequency(&freq);
}
return freq.QuadPart;
}
uint32_t current_thread_id() {
return static_cast<uint32_t>(GetCurrentThreadId());
}

View File

@ -25,6 +25,7 @@
#include "xenia/cpu/backend/x64/x64_sequences.h"
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/backend/x64/x64_emitter.h"
@ -1086,7 +1087,7 @@ EMITTER(LOAD_CLOCK, MATCH(I<OPCODE_LOAD_CLOCK, I64<>>)) {
e.mov(i.dest, e.rax);
}
static uint64_t LoadClock(void* raw_context) {
return xe::threading::ticks();
return Clock::QueryGuestTickCount();
}
};
EMITTER_OPCODE_TABLE(

View File

@ -9,8 +9,11 @@
#include "xenia/emulator.h"
#include <gflags/gflags.h>
#include "xenia/apu/apu.h"
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
#include "xenia/base/string.h"
#include "xenia/cpu/cpu.h"
#include "xenia/gpu/gpu.h"
@ -22,6 +25,9 @@
#include "xenia/memory.h"
#include "xenia/ui/main_window.h"
DEFINE_double(time_scalar, 1.0,
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc).");
namespace xe {
using namespace xe::apu;
@ -64,6 +70,16 @@ Emulator::~Emulator() {
X_STATUS Emulator::Setup() {
X_STATUS result = X_STATUS_UNSUCCESSFUL;
// Initialize clock.
// 360 uses a 50MHz clock.
// Clock::set_guest_tick_frequency(50000000);
// We could reset this with save state data/constant value to help replays.
Clock::set_guest_system_time_base(Clock::QueryHostSystemTime());
// This can be adjusted dynamically, as well.
Clock::set_guest_time_scalar(FLAGS_time_scalar);
// Before we can set thread affinity we must enable the process to use all
// logical processors.
HANDLE process_handle = GetCurrentProcess();
DWORD_PTR process_affinity_mask;
DWORD_PTR system_affinity_mask;

View File

@ -59,7 +59,6 @@ CommandProcessor::CommandProcessor(GL4GraphicsSystem* graphics_system)
trace_state_(TraceState::kDisabled),
worker_running_(true),
swap_mode_(SwapMode::kNormal),
time_base_(0),
counter_(0),
primary_buffer_ptr_(0),
primary_buffer_size_(0),
@ -83,19 +82,10 @@ CommandProcessor::CommandProcessor(GL4GraphicsSystem* graphics_system)
draw_index_count_(0),
draw_batcher_(graphics_system_->register_file()),
scratch_buffer_(kScratchBufferCapacity, kScratchBufferAlignment) {
LARGE_INTEGER perf_counter;
QueryPerformanceCounter(&perf_counter);
time_base_ = perf_counter.QuadPart;
}
CommandProcessor::~CommandProcessor() { CloseHandle(write_ptr_index_event_); }
uint64_t CommandProcessor::QueryTime() {
LARGE_INTEGER perf_counter;
QueryPerformanceCounter(&perf_counter);
return perf_counter.QuadPart - time_base_;
}
bool CommandProcessor::Initialize(std::unique_ptr<GLContext> context) {
context_ = std::move(context);

View File

@ -62,7 +62,6 @@ class CommandProcessor {
typedef std::function<void(const SwapParameters& params)> SwapHandler;
void set_swap_handler(SwapHandler fn) { swap_handler_ = fn; }
uint64_t QueryTime();
uint32_t counter() const { return counter_; }
void increment_counter() { counter_++; }
@ -243,7 +242,6 @@ class CommandProcessor {
SwapMode swap_mode_;
uint64_t time_base_;
uint32_t counter_;
uint32_t primary_buffer_ptr_;

View File

@ -9,6 +9,7 @@
#include "xenia/gpu/gl4/gl4_graphics_system.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/processor.h"
@ -86,11 +87,11 @@ X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor,
kernel::object_ref<kernel::XHostThread>(new kernel::XHostThread(
emulator()->kernel_state(), 128 * 1024, 0, [this]() {
uint64_t vsync_duration = FLAGS_vsync ? 16 : 1;
uint64_t last_frame_time = xe::threading::ticks();
uint64_t last_frame_time = Clock::QueryGuestTickCount();
while (worker_running_) {
uint64_t current_time = xe::threading::ticks();
uint64_t current_time = Clock::QueryGuestTickCount();
uint64_t elapsed = (current_time - last_frame_time) /
(xe::threading::ticks_per_second() / 1000);
(Clock::guest_tick_frequency() / 1000);
if (elapsed >= vsync_duration) {
MarkVblank();
last_frame_time = current_time;

View File

@ -11,6 +11,7 @@
#include "third_party/imgui/imgui.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/main.h"
#include "xenia/base/mapped_memory.h"
@ -2272,10 +2273,10 @@ int trace_viewer_main(std::vector<std::wstring>& args) {
imgui_setup = true;
}
auto& io = ImGui::GetIO();
auto current_ticks = xe::threading::ticks();
auto current_ticks = Clock::QueryHostTickCount();
static uint64_t last_ticks = 0;
io.DeltaTime = (current_ticks - last_ticks) /
float(xe::threading::ticks_per_second());
io.DeltaTime =
(current_ticks - last_ticks) / float(Clock::host_tick_frequency());
last_ticks = current_ticks;
io.DisplaySize =

View File

@ -11,6 +11,7 @@
#include <gflags/gflags.h>
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/mutex.h"
@ -236,10 +237,7 @@ X_STATUS XThread::Create() {
xe::store_and_swap<uint32_t>(p + 0x09C, 0xFDFFD7FF);
xe::store_and_swap<uint32_t>(
p + 0x0D0, thread_state_->stack_address() + thread_state_->stack_size());
FILETIME t;
GetSystemTimeAsFileTime(&t);
xe::store_and_swap<uint64_t>(
p + 0x130, ((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime);
xe::store_and_swap<uint64_t>(p + 0x130, Clock::QueryGuestSystemTime());
xe::store_and_swap<uint32_t>(p + 0x144, thread_state_address_ + 0x144);
xe::store_and_swap<uint32_t>(p + 0x148, thread_state_address_ + 0x144);
xe::store_and_swap<uint32_t>(p + 0x14C, thread_id_);
@ -621,6 +619,7 @@ X_STATUS XThread::Delay(uint32_t processor_mode, uint32_t alertable,
} else {
timeout_ms = 0;
}
timeout_ms = Clock::ScaleGuestDurationMillis(timeout_ms);
DWORD result = SleepEx(timeout_ms, alertable ? TRUE : FALSE);
switch (result) {
case 0:

View File

@ -9,6 +9,7 @@
#include "xenia/kernel/objects/xtimer.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/cpu/processor.h"
@ -49,6 +50,9 @@ X_STATUS XTimer::SetTimer(int64_t due_time, uint32_t period_ms,
current_routine_ = routine;
current_routine_arg_ = routine_arg;
due_time = Clock::ScaleGuestDurationFileTime(due_time);
period_ms = Clock::ScaleGuestDurationMillis(period_ms);
LARGE_INTEGER due_time_li;
due_time_li.QuadPart = due_time;
BOOL result =

View File

@ -10,6 +10,7 @@
#define _WINSOCK_DEPRECATED_NO_WARNINGS // inet_addr
#include <winsock2.h>
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/shim_utils.h"
@ -373,6 +374,7 @@ SHIM_CALL NetDll_select_shim(PPCContext* ppc_state, KernelState* state) {
if (timeout_ptr) {
timeout = {static_cast<long>(SHIM_MEM_32(timeout_ptr + 0)),
static_cast<long>(SHIM_MEM_32(timeout_ptr + 4))};
Clock::ScaleGuestDurationTimeval(&timeout.tv_sec, &timeout.tv_usec);
timeout_in = &timeout;
}
int ret = select(nfds, readfds_ptr ? &readfds : nullptr,

View File

@ -11,6 +11,7 @@
#include <gflags/gflags.h>
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/emulator.h"
@ -133,13 +134,15 @@ XboxkrnlModule::XboxkrnlModule(Emulator* emulator, KernelState* kernel_state)
"xboxkrnl.exe", ordinals::KeTimeStampBundle, pKeTimeStampBundle);
xe::store_and_swap<uint64_t>(lpKeTimeStampBundle + 0, 0);
xe::store_and_swap<uint64_t>(lpKeTimeStampBundle + 8, 0);
xe::store_and_swap<uint32_t>(lpKeTimeStampBundle + 16, GetTickCount());
xe::store_and_swap<uint32_t>(lpKeTimeStampBundle + 16,
Clock::QueryGuestUptimeMillis());
xe::store_and_swap<uint32_t>(lpKeTimeStampBundle + 20, 0);
CreateTimerQueueTimer(
&timestamp_timer_, nullptr,
[](PVOID param, BOOLEAN timer_or_wait_fired) {
auto timestamp_bundle = reinterpret_cast<uint8_t*>(param);
xe::store_and_swap<uint32_t>(timestamp_bundle + 16, GetTickCount());
xe::store_and_swap<uint32_t>(timestamp_bundle + 16,
Clock::QueryGuestUptimeMillis());
},
lpKeTimeStampBundle, 0,
1, // 1ms

View File

@ -8,6 +8,7 @@
*/
#include "xenia/base/atomic.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/mutex.h"
#include "xenia/cpu/processor.h"
@ -312,13 +313,7 @@ SHIM_CALL KeQueryPerformanceFrequency_shim(PPCContext* ppc_state,
// XELOGD(
// "KeQueryPerformanceFrequency()");
// TODO(benvanik): return fixed 50000000?
uint64_t result = 0;
LARGE_INTEGER frequency;
if (QueryPerformanceFrequency(&frequency)) {
result = frequency.QuadPart;
}
uint64_t result = Clock::guest_tick_frequency();
SHIM_SET_RETURN_64(result);
}
@ -351,9 +346,7 @@ SHIM_CALL KeQuerySystemTime_shim(PPCContext* ppc_state, KernelState* state) {
XELOGD("KeQuerySystemTime(%.8X)", time_ptr);
FILETIME t;
GetSystemTimeAsFileTime(&t);
uint64_t time = ((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime;
uint64_t time = Clock::QueryGuestSystemTime();
if (time_ptr) {
SHIM_SET_MEM_64(time_ptr, time);

View File

@ -9,6 +9,7 @@
#include "xenia/kernel/xobject.h"
#include "xenia/base/clock.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/objects/xevent.h"
#include "xenia/kernel/objects/xmutant.h"
@ -109,6 +110,7 @@ X_STATUS XObject::Wait(uint32_t wait_reason, uint32_t processor_mode,
}
DWORD timeout_ms = opt_timeout ? TimeoutTicksToMs(*opt_timeout) : INFINITE;
timeout_ms = Clock::ScaleGuestDurationMillis(timeout_ms);
DWORD result = WaitForSingleObjectEx(wait_handle, timeout_ms, alertable);
switch (result) {
@ -131,6 +133,7 @@ X_STATUS XObject::SignalAndWait(XObject* signal_object, XObject* wait_object,
uint32_t wait_reason, uint32_t processor_mode,
uint32_t alertable, uint64_t* opt_timeout) {
DWORD timeout_ms = opt_timeout ? TimeoutTicksToMs(*opt_timeout) : INFINITE;
timeout_ms = Clock::ScaleGuestDurationMillis(timeout_ms);
DWORD result = SignalObjectAndWait(signal_object->GetWaitHandle(),
wait_object->GetWaitHandle(), timeout_ms,
@ -150,6 +153,7 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
}
DWORD timeout_ms = opt_timeout ? TimeoutTicksToMs(*opt_timeout) : INFINITE;
timeout_ms = Clock::ScaleGuestDurationMillis(timeout_ms);
DWORD result = WaitForMultipleObjectsEx(
count, wait_handles, wait_type ? FALSE : TRUE, timeout_ms, alertable);

View File

@ -14,6 +14,7 @@
#include <algorithm>
#include <mutex>
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/threading.h"
@ -114,7 +115,7 @@ Memory::~Memory() {
int Memory::Initialize() {
wchar_t file_name[256];
wsprintf(file_name, L"Local\\xenia_memory_%p", xe::threading::ticks());
wsprintf(file_name, L"Local\\xenia_memory_%p", Clock::QueryHostTickCount());
file_name_ = file_name;
// Create main page file-backed mapping. This is all reserved but