Merge pull request #11301 from AdmiralCurtiss/globals-coretiming

CoreTiming: Move variables to Core::System.
This commit is contained in:
Mai 2022-11-26 06:50:47 +00:00 committed by GitHub
commit ed84917eb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 217 additions and 106 deletions

View File

@ -50,38 +50,46 @@ static bool operator<(const Event& left, const Event& right)
return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order); return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order);
} }
// unordered_map stores each element separately as a linked list node so pointers to elements
// remain stable regardless of rehashes/resizing.
static std::unordered_map<std::string, EventType> s_event_types;
// STATE_TO_SAVE
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't accomodated
// by the standard adaptor class.
static std::vector<Event> s_event_queue;
static u64 s_event_fifo_id;
static std::mutex s_ts_write_lock;
static Common::SPSCQueue<Event, false> s_ts_queue;
static float s_last_OC_factor;
static constexpr int MAX_SLICE_LENGTH = 20000; static constexpr int MAX_SLICE_LENGTH = 20000;
static s64 s_idled_cycles; struct CoreTimingState::Data
static u32 s_fake_dec_start_value; {
static u64 s_fake_dec_start_ticks; // unordered_map stores each element separately as a linked list node so pointers to elements
// remain stable regardless of rehashes/resizing.
std::unordered_map<std::string, EventType> event_types;
// Are we in a function that has been called from Advance() // STATE_TO_SAVE
static bool s_is_global_timer_sane; // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't accomodated
// by the standard adaptor class.
std::vector<Event> event_queue;
u64 event_fifo_id;
std::mutex ts_write_lock;
Common::SPSCQueue<Event, false> ts_queue;
Globals g; float last_oc_factor;
static EventType* s_ev_lost = nullptr; s64 idled_cycles;
u32 fake_dec_start_value;
u64 fake_dec_start_ticks;
static size_t s_registered_config_callback_id; // Are we in a function that has been called from Advance()
static float s_config_OC_factor; bool is_global_timer_sane;
static float s_config_OC_inv_factor;
static bool s_config_sync_on_skip_idle; EventType* ev_lost = nullptr;
size_t registered_config_callback_id;
float config_oc_factor;
float config_oc_inv_factor;
bool config_sync_on_skip_idle;
};
CoreTimingState::CoreTimingState() : m_data(std::make_unique<Data>())
{
}
CoreTimingState::~CoreTimingState() = default;
static void EmptyTimedCallback(Core::System& system, u64 userdata, s64 cyclesLate) static void EmptyTimedCallback(Core::System& system, u64 userdata, s64 cyclesLate)
{ {
@ -94,26 +102,28 @@ static void EmptyTimedCallback(Core::System& system, u64 userdata, s64 cyclesLat
// //
// Technically it might be more accurate to call this changing the IPC instead of the CPU speed, // Technically it might be more accurate to call this changing the IPC instead of the CPU speed,
// but the effect is largely the same. // but the effect is largely the same.
static int DowncountToCycles(int downcount) static int DowncountToCycles(CoreTiming::Globals& g, int downcount)
{ {
return static_cast<int>(downcount * g.last_OC_factor_inverted); return static_cast<int>(downcount * g.last_OC_factor_inverted);
} }
static int CyclesToDowncount(int cycles) static int CyclesToDowncount(CoreTiming::CoreTimingState::Data& state, int cycles)
{ {
return static_cast<int>(cycles * s_last_OC_factor); return static_cast<int>(cycles * state.last_oc_factor);
} }
EventType* RegisterEvent(const std::string& name, TimedCallback callback) EventType* RegisterEvent(const std::string& name, TimedCallback callback)
{ {
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
// check for existing type with same name. // check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization. // we want event type names to remain unique so that we can use them for serialization.
ASSERT_MSG(POWERPC, s_event_types.find(name) == s_event_types.end(), ASSERT_MSG(POWERPC, state.event_types.find(name) == state.event_types.end(),
"CoreTiming Event \"{}\" is already registered. Events should only be registered " "CoreTiming Event \"{}\" is already registered. Events should only be registered "
"during Init to avoid breaking save states.", "during Init to avoid breaking save states.",
name); name);
auto info = s_event_types.emplace(name, EventType{callback, nullptr}); auto info = state.event_types.emplace(name, EventType{callback, nullptr});
EventType* event_type = &info.first->second; EventType* event_type = &info.first->second;
event_type->name = &info.first->first; event_type->name = &info.first->first;
return event_type; return event_type;
@ -121,68 +131,80 @@ EventType* RegisterEvent(const std::string& name, TimedCallback callback)
void UnregisterAllEvents() void UnregisterAllEvents()
{ {
ASSERT_MSG(POWERPC, s_event_queue.empty(), "Cannot unregister events with events pending"); auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
s_event_types.clear();
ASSERT_MSG(POWERPC, state.event_queue.empty(), "Cannot unregister events with events pending");
state.event_types.clear();
} }
void Init() void Init()
{ {
s_registered_config_callback_id = auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
state.registered_config_callback_id =
Config::AddConfigChangedCallback([]() { Core::RunAsCPUThread([]() { RefreshConfig(); }); }); Config::AddConfigChangedCallback([]() { Core::RunAsCPUThread([]() { RefreshConfig(); }); });
RefreshConfig(); RefreshConfig();
s_last_OC_factor = s_config_OC_factor; state.last_oc_factor = state.config_oc_factor;
g.last_OC_factor_inverted = s_config_OC_inv_factor; g.last_OC_factor_inverted = state.config_oc_inv_factor;
PowerPC::ppcState.downcount = CyclesToDowncount(MAX_SLICE_LENGTH); PowerPC::ppcState.downcount = CyclesToDowncount(state, MAX_SLICE_LENGTH);
g.slice_length = MAX_SLICE_LENGTH; g.slice_length = MAX_SLICE_LENGTH;
g.global_timer = 0; g.global_timer = 0;
s_idled_cycles = 0; state.idled_cycles = 0;
// The time between CoreTiming being intialized and the first call to Advance() is considered // The time between CoreTiming being intialized and the first call to Advance() is considered
// the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before // the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
// executing the first PPC cycle of each slice to prepare the slice length and downcount for // executing the first PPC cycle of each slice to prepare the slice length and downcount for
// that slice. // that slice.
s_is_global_timer_sane = true; state.is_global_timer_sane = true;
s_event_fifo_id = 0; state.event_fifo_id = 0;
s_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback); state.ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
} }
void Shutdown() void Shutdown()
{ {
std::lock_guard lk(s_ts_write_lock); auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
std::lock_guard lk(state.ts_write_lock);
MoveEvents(); MoveEvents();
ClearPendingEvents(); ClearPendingEvents();
UnregisterAllEvents(); UnregisterAllEvents();
Config::RemoveConfigChangedCallback(s_registered_config_callback_id); Config::RemoveConfigChangedCallback(state.registered_config_callback_id);
} }
void RefreshConfig() void RefreshConfig()
{ {
s_config_OC_factor = auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
state.config_oc_factor =
Config::Get(Config::MAIN_OVERCLOCK_ENABLE) ? Config::Get(Config::MAIN_OVERCLOCK) : 1.0f; Config::Get(Config::MAIN_OVERCLOCK_ENABLE) ? Config::Get(Config::MAIN_OVERCLOCK) : 1.0f;
s_config_OC_inv_factor = 1.0f / s_config_OC_factor; state.config_oc_inv_factor = 1.0f / state.config_oc_factor;
s_config_sync_on_skip_idle = Config::Get(Config::MAIN_SYNC_ON_SKIP_IDLE); state.config_sync_on_skip_idle = Config::Get(Config::MAIN_SYNC_ON_SKIP_IDLE);
} }
void DoState(PointerWrap& p) void DoState(PointerWrap& p)
{ {
std::lock_guard lk(s_ts_write_lock); auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
std::lock_guard lk(state.ts_write_lock);
p.Do(g.slice_length); p.Do(g.slice_length);
p.Do(g.global_timer); p.Do(g.global_timer);
p.Do(s_idled_cycles); p.Do(state.idled_cycles);
p.Do(s_fake_dec_start_value); p.Do(state.fake_dec_start_value);
p.Do(s_fake_dec_start_ticks); p.Do(state.fake_dec_start_ticks);
p.Do(g.fake_TB_start_value); p.Do(g.fake_TB_start_value);
p.Do(g.fake_TB_start_ticks); p.Do(g.fake_TB_start_ticks);
p.Do(s_last_OC_factor); p.Do(state.last_oc_factor);
g.last_OC_factor_inverted = 1.0f / s_last_OC_factor; g.last_OC_factor_inverted = 1.0f / state.last_oc_factor;
p.Do(s_event_fifo_id); p.Do(state.event_fifo_id);
p.DoMarker("CoreTimingData"); p.DoMarker("CoreTimingData");
MoveEvents(); MoveEvents();
p.DoEachElement(s_event_queue, [](PointerWrap& pw, Event& ev) { p.DoEachElement(state.event_queue, [&state](PointerWrap& pw, Event& ev) {
pw.Do(ev.time); pw.Do(ev.time);
pw.Do(ev.fifo_order); pw.Do(ev.fifo_order);
@ -199,8 +221,8 @@ void DoState(PointerWrap& p)
pw.Do(name); pw.Do(name);
if (pw.IsReadMode()) if (pw.IsReadMode())
{ {
auto itr = s_event_types.find(name); auto itr = state.event_types.find(name);
if (itr != s_event_types.end()) if (itr != state.event_types.end())
{ {
ev.type = &itr->second; ev.type = &itr->second;
} }
@ -209,7 +231,7 @@ void DoState(PointerWrap& p)
WARN_LOG_FMT(POWERPC, WARN_LOG_FMT(POWERPC,
"Lost event from savestate because its type, \"{}\", has not been registered.", "Lost event from savestate because its type, \"{}\", has not been registered.",
name); name);
ev.type = s_ev_lost; ev.type = state.ev_lost;
} }
} }
}); });
@ -219,17 +241,21 @@ void DoState(PointerWrap& p)
// The exact layout of the heap in memory is implementation defined, therefore it is platform // The exact layout of the heap in memory is implementation defined, therefore it is platform
// and library version specific. // and library version specific.
if (p.IsReadMode()) if (p.IsReadMode())
std::make_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>()); std::make_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
} }
// This should only be called from the CPU thread. If you are calling // This should only be called from the CPU thread. If you are calling
// it from any other thread, you are doing something evil // it from any other thread, you are doing something evil
u64 GetTicks() u64 GetTicks()
{ {
auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
u64 ticks = static_cast<u64>(g.global_timer); u64 ticks = static_cast<u64>(g.global_timer);
if (!s_is_global_timer_sane) if (!state.is_global_timer_sane)
{ {
int downcount = DowncountToCycles(PowerPC::ppcState.downcount); int downcount = DowncountToCycles(g, PowerPC::ppcState.downcount);
ticks += g.slice_length - downcount; ticks += g.slice_length - downcount;
} }
return ticks; return ticks;
@ -237,18 +263,24 @@ u64 GetTicks()
u64 GetIdleTicks() u64 GetIdleTicks()
{ {
return static_cast<u64>(s_idled_cycles); auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
return static_cast<u64>(state.idled_cycles);
} }
void ClearPendingEvents() void ClearPendingEvents()
{ {
s_event_queue.clear(); auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
state.event_queue.clear();
} }
void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata, FromThread from) void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata, FromThread from)
{ {
ASSERT_MSG(POWERPC, event_type, "Event type is nullptr, will crash now."); ASSERT_MSG(POWERPC, event_type, "Event type is nullptr, will crash now.");
auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
bool from_cpu_thread; bool from_cpu_thread;
if (from == FromThread::ANY) if (from == FromThread::ANY)
{ {
@ -267,11 +299,11 @@ void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata,
s64 timeout = GetTicks() + cycles_into_future; s64 timeout = GetTicks() + cycles_into_future;
// If this event needs to be scheduled before the next advance(), force one early // If this event needs to be scheduled before the next advance(), force one early
if (!s_is_global_timer_sane) if (!state.is_global_timer_sane)
ForceExceptionCheck(cycles_into_future); ForceExceptionCheck(cycles_into_future);
s_event_queue.emplace_back(Event{timeout, s_event_fifo_id++, userdata, event_type}); state.event_queue.emplace_back(Event{timeout, state.event_fifo_id++, userdata, event_type});
std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>()); std::push_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
} }
else else
{ {
@ -283,21 +315,23 @@ void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata,
*event_type->name); *event_type->name);
} }
std::lock_guard lk(s_ts_write_lock); std::lock_guard lk(state.ts_write_lock);
s_ts_queue.Push(Event{g.global_timer + cycles_into_future, 0, userdata, event_type}); state.ts_queue.Push(Event{g.global_timer + cycles_into_future, 0, userdata, event_type});
} }
} }
void RemoveEvent(EventType* event_type) void RemoveEvent(EventType* event_type)
{ {
auto itr = std::remove_if(s_event_queue.begin(), s_event_queue.end(), auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
auto itr = std::remove_if(state.event_queue.begin(), state.event_queue.end(),
[&](const Event& e) { return e.type == event_type; }); [&](const Event& e) { return e.type == event_type; });
// Removing random items breaks the invariant so we have to re-establish it. // Removing random items breaks the invariant so we have to re-establish it.
if (itr != s_event_queue.end()) if (itr != state.event_queue.end())
{ {
s_event_queue.erase(itr, s_event_queue.end()); state.event_queue.erase(itr, state.event_queue.end());
std::make_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>()); std::make_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
} }
} }
@ -309,56 +343,65 @@ void RemoveAllEvents(EventType* event_type)
void ForceExceptionCheck(s64 cycles) void ForceExceptionCheck(s64 cycles)
{ {
auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
cycles = std::max<s64>(0, cycles); cycles = std::max<s64>(0, cycles);
if (DowncountToCycles(PowerPC::ppcState.downcount) > cycles) if (DowncountToCycles(g, PowerPC::ppcState.downcount) > cycles)
{ {
// downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int here. // downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int here.
// Account for cycles already executed by adjusting the g.slice_length // Account for cycles already executed by adjusting the g.slice_length
g.slice_length -= DowncountToCycles(PowerPC::ppcState.downcount) - static_cast<int>(cycles); g.slice_length -= DowncountToCycles(g, PowerPC::ppcState.downcount) - static_cast<int>(cycles);
PowerPC::ppcState.downcount = CyclesToDowncount(static_cast<int>(cycles)); PowerPC::ppcState.downcount = CyclesToDowncount(state, static_cast<int>(cycles));
} }
} }
void MoveEvents() void MoveEvents()
{ {
for (Event ev; s_ts_queue.Pop(ev);) auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
for (Event ev; state.ts_queue.Pop(ev);)
{ {
ev.fifo_order = s_event_fifo_id++; ev.fifo_order = state.event_fifo_id++;
s_event_queue.emplace_back(std::move(ev)); state.event_queue.emplace_back(std::move(ev));
std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>()); std::push_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
} }
} }
void Advance() void Advance()
{ {
auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
MoveEvents(); MoveEvents();
int cyclesExecuted = g.slice_length - DowncountToCycles(PowerPC::ppcState.downcount); int cyclesExecuted = g.slice_length - DowncountToCycles(g, PowerPC::ppcState.downcount);
g.global_timer += cyclesExecuted; g.global_timer += cyclesExecuted;
s_last_OC_factor = s_config_OC_factor; state.last_oc_factor = state.config_oc_factor;
g.last_OC_factor_inverted = s_config_OC_inv_factor; g.last_OC_factor_inverted = state.config_oc_inv_factor;
g.slice_length = MAX_SLICE_LENGTH; g.slice_length = MAX_SLICE_LENGTH;
s_is_global_timer_sane = true; state.is_global_timer_sane = true;
while (!s_event_queue.empty() && s_event_queue.front().time <= g.global_timer) while (!state.event_queue.empty() && state.event_queue.front().time <= g.global_timer)
{ {
Event evt = std::move(s_event_queue.front()); Event evt = std::move(state.event_queue.front());
std::pop_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>()); std::pop_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
s_event_queue.pop_back(); state.event_queue.pop_back();
evt.type->callback(Core::System::GetInstance(), evt.userdata, g.global_timer - evt.time); evt.type->callback(system, evt.userdata, g.global_timer - evt.time);
} }
s_is_global_timer_sane = false; state.is_global_timer_sane = false;
// Still events left (scheduled in the future) // Still events left (scheduled in the future)
if (!s_event_queue.empty()) if (!state.event_queue.empty())
{ {
g.slice_length = static_cast<int>( g.slice_length = static_cast<int>(
std::min<s64>(s_event_queue.front().time - g.global_timer, MAX_SLICE_LENGTH)); std::min<s64>(state.event_queue.front().time - g.global_timer, MAX_SLICE_LENGTH));
} }
PowerPC::ppcState.downcount = CyclesToDowncount(g.slice_length); PowerPC::ppcState.downcount = CyclesToDowncount(state, g.slice_length);
// Check for any external exceptions. // Check for any external exceptions.
// It's important to do this after processing events otherwise any exceptions will be delayed // It's important to do this after processing events otherwise any exceptions will be delayed
@ -369,7 +412,11 @@ void Advance()
void LogPendingEvents() void LogPendingEvents()
{ {
auto clone = s_event_queue; auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
auto clone = state.event_queue;
std::sort(clone.begin(), clone.end()); std::sort(clone.begin(), clone.end());
for (const Event& ev : clone) for (const Event& ev : clone)
{ {
@ -381,7 +428,11 @@ void LogPendingEvents()
// Should only be called from the CPU thread after the PPC clock has changed // Should only be called from the CPU thread after the PPC clock has changed
void AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock) void AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock)
{ {
for (Event& ev : s_event_queue) auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
for (Event& ev : state.event_queue)
{ {
const s64 ticks = (ev.time - g.global_timer) * new_ppc_clock / old_ppc_clock; const s64 ticks = (ev.time - g.global_timer) * new_ppc_clock / old_ppc_clock;
ev.time = g.global_timer + ticks; ev.time = g.global_timer + ticks;
@ -390,7 +441,11 @@ void AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock)
void Idle() void Idle()
{ {
if (s_config_sync_on_skip_idle) auto& system = Core::System::GetInstance();
auto& state = system.GetCoreTimingState().GetData();
auto& g = system.GetCoreTimingGlobals();
if (state.config_sync_on_skip_idle)
{ {
// When the FIFO is processing data we must not advance because in this way // When the FIFO is processing data we must not advance because in this way
// the VI will be desynchronized. So, We are waiting until the FIFO finish and // the VI will be desynchronized. So, We are waiting until the FIFO finish and
@ -399,16 +454,18 @@ void Idle()
} }
PowerPC::UpdatePerformanceMonitor(PowerPC::ppcState.downcount, 0, 0); PowerPC::UpdatePerformanceMonitor(PowerPC::ppcState.downcount, 0, 0);
s_idled_cycles += DowncountToCycles(PowerPC::ppcState.downcount); state.idled_cycles += DowncountToCycles(g, PowerPC::ppcState.downcount);
PowerPC::ppcState.downcount = 0; PowerPC::ppcState.downcount = 0;
} }
std::string GetScheduledEventsSummary() std::string GetScheduledEventsSummary()
{ {
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
std::string text = "Scheduled events\n"; std::string text = "Scheduled events\n";
text.reserve(1000); text.reserve(1000);
auto clone = s_event_queue; auto clone = state.event_queue;
std::sort(clone.begin(), clone.end()); std::sort(clone.begin(), clone.end());
for (const Event& ev : clone) for (const Event& ev : clone)
{ {
@ -419,41 +476,49 @@ std::string GetScheduledEventsSummary()
u32 GetFakeDecStartValue() u32 GetFakeDecStartValue()
{ {
return s_fake_dec_start_value; auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
return state.fake_dec_start_value;
} }
void SetFakeDecStartValue(u32 val) void SetFakeDecStartValue(u32 val)
{ {
s_fake_dec_start_value = val; auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
state.fake_dec_start_value = val;
} }
u64 GetFakeDecStartTicks() u64 GetFakeDecStartTicks()
{ {
return s_fake_dec_start_ticks; auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
return state.fake_dec_start_ticks;
} }
void SetFakeDecStartTicks(u64 val) void SetFakeDecStartTicks(u64 val)
{ {
s_fake_dec_start_ticks = val; auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
state.fake_dec_start_ticks = val;
} }
u64 GetFakeTBStartValue() u64 GetFakeTBStartValue()
{ {
auto& g = Core::System::GetInstance().GetCoreTimingGlobals();
return g.fake_TB_start_value; return g.fake_TB_start_value;
} }
void SetFakeTBStartValue(u64 val) void SetFakeTBStartValue(u64 val)
{ {
auto& g = Core::System::GetInstance().GetCoreTimingGlobals();
g.fake_TB_start_value = val; g.fake_TB_start_value = val;
} }
u64 GetFakeTBStartTicks() u64 GetFakeTBStartTicks()
{ {
auto& g = Core::System::GetInstance().GetCoreTimingGlobals();
return g.fake_TB_start_ticks; return g.fake_TB_start_ticks;
} }
void SetFakeTBStartTicks(u64 val) void SetFakeTBStartTicks(u64 val)
{ {
auto& g = Core::System::GetInstance().GetCoreTimingGlobals();
g.fake_TB_start_ticks = val; g.fake_TB_start_ticks = val;
} }

View File

@ -16,7 +16,9 @@
// inside callback: // inside callback:
// ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever") // ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever")
#include <memory>
#include <string> #include <string>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
class PointerWrap; class PointerWrap;
@ -28,6 +30,23 @@ class System;
namespace CoreTiming namespace CoreTiming
{ {
class CoreTimingState
{
public:
CoreTimingState();
CoreTimingState(const CoreTimingState&) = delete;
CoreTimingState(CoreTimingState&&) = delete;
CoreTimingState& operator=(const CoreTimingState&) = delete;
CoreTimingState& operator=(CoreTimingState&&) = delete;
~CoreTimingState();
struct Data;
Data& GetData() { return *m_data; }
private:
std::unique_ptr<Data> m_data;
};
// These really shouldn't be global, but jit64 accesses them directly // These really shouldn't be global, but jit64 accesses them directly
struct Globals struct Globals
{ {
@ -37,7 +56,6 @@ struct Globals
u64 fake_TB_start_ticks; u64 fake_TB_start_ticks;
float last_OC_factor_inverted; float last_OC_factor_inverted;
}; };
extern Globals g;
// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is // CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
// required to end slice -1 and start slice 0 before the first cycle of code is executed. // required to end slice -1 and start slice 0 before the first cycle of code is executed.

View File

@ -212,13 +212,15 @@ int Interpreter::SingleStepInner()
void Interpreter::SingleStep() void Interpreter::SingleStep()
{ {
auto& core_timing_globals = Core::System::GetInstance().GetCoreTimingGlobals();
// Declare start of new slice // Declare start of new slice
CoreTiming::Advance(); CoreTiming::Advance();
SingleStepInner(); SingleStepInner();
// The interpreter ignores instruction timing information outside the 'fast runloop'. // The interpreter ignores instruction timing information outside the 'fast runloop'.
CoreTiming::g.slice_length = 1; core_timing_globals.slice_length = 1;
PowerPC::ppcState.downcount = 0; PowerPC::ppcState.downcount = 0;
if (PowerPC::ppcState.Exceptions != 0) if (PowerPC::ppcState.Exceptions != 0)

View File

@ -14,6 +14,7 @@
#include "Core/PowerPC/Jit64/RegCache/JitRegCache.h" #include "Core/PowerPC/Jit64/RegCache/JitRegCache.h"
#include "Core/PowerPC/Jit64Common/Jit64PowerPCState.h" #include "Core/PowerPC/Jit64Common/Jit64PowerPCState.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
using namespace Gen; using namespace Gen;
@ -322,7 +323,8 @@ void Jit64::mfspr(UGeckoInstruction inst)
RCX64Reg rax = gpr.Scratch(RAX); RCX64Reg rax = gpr.Scratch(RAX);
RCX64Reg rcx = gpr.Scratch(RCX); RCX64Reg rcx = gpr.Scratch(RCX);
MOV(64, rcx, ImmPtr(&CoreTiming::g)); auto& core_timing_globals = Core::System::GetInstance().GetCoreTimingGlobals();
MOV(64, rcx, ImmPtr(&core_timing_globals));
// An inline implementation of CoreTiming::GetFakeTimeBase, since in timer-heavy games the // An inline implementation of CoreTiming::GetFakeTimeBase, since in timer-heavy games the
// cost of calling out to C for this is actually significant. // cost of calling out to C for this is actually significant.

View File

@ -13,6 +13,7 @@
#include "Core/PowerPC/Interpreter/ExceptionUtils.h" #include "Core/PowerPC/Interpreter/ExceptionUtils.h"
#include "Core/PowerPC/PPCTables.h" #include "Core/PowerPC/PPCTables.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
using namespace Arm64Gen; using namespace Arm64Gen;
@ -306,7 +307,8 @@ void JitArm64::mfspr(UGeckoInstruction inst)
// An inline implementation of CoreTiming::GetFakeTimeBase, since in timer-heavy games the // An inline implementation of CoreTiming::GetFakeTimeBase, since in timer-heavy games the
// cost of calling out to C for this is actually significant. // cost of calling out to C for this is actually significant.
MOVP2R(Xg, &CoreTiming::g); auto& core_timing_globals = Core::System::GetInstance().GetCoreTimingGlobals();
MOVP2R(Xg, &core_timing_globals);
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(downcount)); LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(downcount));
m_float_emit.SCVTF(SC, WA); m_float_emit.SCVTF(SC, WA);

View File

@ -7,6 +7,7 @@
#include "AudioCommon/SoundStream.h" #include "AudioCommon/SoundStream.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/CoreTiming.h"
#include "Core/HW/AudioInterface.h" #include "Core/HW/AudioInterface.h"
#include "Core/HW/DSP.h" #include "Core/HW/DSP.h"
#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/DVD/DVDInterface.h"
@ -26,6 +27,8 @@ struct System::Impl
bool m_audio_dump_started = false; bool m_audio_dump_started = false;
AudioInterface::AudioInterfaceState m_audio_interface_state; AudioInterface::AudioInterfaceState m_audio_interface_state;
CoreTiming::CoreTimingState m_core_timing_state;
CoreTiming::Globals m_core_timing_globals;
DSP::DSPState m_dsp_state; DSP::DSPState m_dsp_state;
DVDInterface::DVDInterfaceState m_dvd_interface_state; DVDInterface::DVDInterfaceState m_dvd_interface_state;
DVDThread::DVDThreadState m_dvd_thread_state; DVDThread::DVDThreadState m_dvd_thread_state;
@ -84,6 +87,16 @@ AudioInterface::AudioInterfaceState& System::GetAudioInterfaceState() const
return m_impl->m_audio_interface_state; return m_impl->m_audio_interface_state;
} }
CoreTiming::CoreTimingState& System::GetCoreTimingState() const
{
return m_impl->m_core_timing_state;
}
CoreTiming::Globals& System::GetCoreTimingGlobals() const
{
return m_impl->m_core_timing_globals;
}
DSP::DSPState& System::GetDSPState() const DSP::DSPState& System::GetDSPState() const
{ {
return m_impl->m_dsp_state; return m_impl->m_dsp_state;

View File

@ -12,6 +12,11 @@ namespace AudioInterface
{ {
class AudioInterfaceState; class AudioInterfaceState;
}; };
namespace CoreTiming
{
class CoreTimingState;
struct Globals;
} // namespace CoreTiming
namespace DSP namespace DSP
{ {
class DSPState; class DSPState;
@ -76,6 +81,8 @@ public:
void SetAudioDumpStarted(bool started); void SetAudioDumpStarted(bool started);
AudioInterface::AudioInterfaceState& GetAudioInterfaceState() const; AudioInterface::AudioInterfaceState& GetAudioInterfaceState() const;
CoreTiming::CoreTimingState& GetCoreTimingState() const;
CoreTiming::Globals& GetCoreTimingGlobals() const;
DSP::DSPState& GetDSPState() const; DSP::DSPState& GetDSPState() const;
DVDInterface::DVDInterfaceState& GetDVDInterfaceState() const; DVDInterface::DVDInterfaceState& GetDVDInterfaceState() const;
DVDThread::DVDThreadState& GetDVDThreadState() const; DVDThread::DVDThreadState& GetDVDThreadState() const;

View File

@ -14,6 +14,7 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/CoreTiming.h" #include "Core/CoreTiming.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "UICommon/UICommon.h" #include "UICommon/UICommon.h"
// Numbers are chosen randomly to make sure the correct one is given. // Numbers are chosen randomly to make sure the correct one is given.
@ -279,9 +280,10 @@ TEST(CoreTiming, ScheduleIntoPast)
// the stale value, i.e. effectively half-way through the previous slice. // the stale value, i.e. effectively half-way through the previous slice.
// NOTE: We're only testing that the scheduler doesn't break, not whether this makes sense. // NOTE: We're only testing that the scheduler doesn't break, not whether this makes sense.
Core::UndeclareAsCPUThread(); Core::UndeclareAsCPUThread();
CoreTiming::g.global_timer -= 1000; auto& core_timing_globals = Core::System::GetInstance().GetCoreTimingGlobals();
core_timing_globals.global_timer -= 1000;
CoreTiming::ScheduleEvent(0, cb_b, CB_IDS[1], CoreTiming::FromThread::NON_CPU); CoreTiming::ScheduleEvent(0, cb_b, CB_IDS[1], CoreTiming::FromThread::NON_CPU);
CoreTiming::g.global_timer += 1000; core_timing_globals.global_timer += 1000;
Core::DeclareAsCPUThread(); Core::DeclareAsCPUThread();
AdvanceAndCheck(1, MAX_SLICE_LENGTH, MAX_SLICE_LENGTH + 1000); AdvanceAndCheck(1, MAX_SLICE_LENGTH, MAX_SLICE_LENGTH + 1000);