Merge pull request #4168 from EmptyChaos/coretiming-cleanup

Core: CoreTiming Cleanup (Add UnitTests)
This commit is contained in:
Scott Mansell 2016-09-04 11:48:54 +12:00 committed by GitHub
commit f5fa5a7d32
27 changed files with 629 additions and 403 deletions

View File

@ -51,12 +51,6 @@
#error No version of is_trivially_copyable
#endif
template <class T>
struct LinkedListItem : public T
{
LinkedListItem<T>* next;
};
// Wrapper class
class PointerWrap
{
@ -244,67 +238,6 @@ public:
}
}
// Let's pretend std::list doesn't exist!
template <class T, LinkedListItem<T>* (*TNew)(), void (*TFree)(LinkedListItem<T>*),
void (*TDo)(PointerWrap&, T*)>
void DoLinkedList(LinkedListItem<T>*& list_start, LinkedListItem<T>** list_end = 0)
{
LinkedListItem<T>* list_cur = list_start;
LinkedListItem<T>* prev = nullptr;
while (true)
{
u8 shouldExist = !!list_cur;
Do(shouldExist);
if (shouldExist == 1)
{
LinkedListItem<T>* cur = list_cur ? list_cur : TNew();
TDo(*this, (T*)cur);
if (!list_cur)
{
if (mode == MODE_READ)
{
cur->next = nullptr;
list_cur = cur;
if (prev)
prev->next = cur;
else
list_start = cur;
}
else
{
TFree(cur);
continue;
}
}
}
else
{
if (mode == MODE_READ)
{
if (prev)
prev->next = nullptr;
if (list_end)
*list_end = prev;
if (list_cur)
{
if (list_start == list_cur)
list_start = nullptr;
do
{
LinkedListItem<T>* next = list_cur->next;
TFree(list_cur);
list_cur = next;
} while (list_cur);
}
}
break;
}
prev = list_cur;
list_cur = list_cur->next;
}
}
void DoMarker(const std::string& prevName, u32 arbitraryNumber = 0x42)
{
u32 cookie = arbitraryNumber;
@ -319,16 +252,22 @@ public:
}
}
template <typename T, typename Functor>
void DoEachElement(T& container, Functor member)
{
u32 size = static_cast<u32>(container.size());
Do(size);
container.resize(size);
for (auto& elem : container)
member(*this, elem);
}
private:
template <typename T>
void DoContainer(T& x)
{
u32 size = (u32)x.size();
Do(size);
x.resize(size);
for (auto& elem : x)
Do(elem);
DoEachElement(x, [](PointerWrap& p, typename T::value_type& elem) { p.Do(elem); });
}
__forceinline void DoVoid(void* data, u32 size)

View File

@ -2,13 +2,17 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <cinttypes>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include "Common/FifoQueue.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"
@ -20,68 +24,60 @@
#include "VideoCommon/Fifo.h"
#include "VideoCommon/VideoBackendBase.h"
#define MAX_SLICE_LENGTH 20000
namespace CoreTiming
{
struct EventType
{
TimedCallback callback;
std::string name;
const std::string* name;
};
static std::vector<EventType> event_types;
struct BaseEvent
struct Event
{
s64 time;
u64 userdata;
int type;
EventType* type;
};
typedef LinkedListItem<BaseEvent> Event;
constexpr bool operator>(const Event& left, const Event& right)
{
return left.time > right.time;
}
constexpr bool operator<(const Event& left, const Event& right)
{
return left.time < right.time;
}
// 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
static Event* first;
static std::mutex tsWriteLock;
static Common::FifoQueue<BaseEvent, false> tsQueue;
// 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 std::mutex s_ts_write_lock;
static Common::FifoQueue<Event, false> s_ts_queue;
// event pools
static Event* eventPool = nullptr;
static float s_last_OC_factor;
float g_last_OC_factor_inverted;
int g_slice_length;
static constexpr int MAX_SLICE_LENGTH = 20000;
static float s_lastOCFactor;
float g_lastOCFactor_inverted;
int g_slicelength;
static int maxslicelength = MAX_SLICE_LENGTH;
static s64 idledCycles;
static u32 fakeDecStartValue;
static u64 fakeDecStartTicks;
static s64 s_idled_cycles;
static u32 s_fake_dec_start_value;
static u64 s_fake_dec_start_ticks;
// Are we in a function that has been called from Advance()
static bool globalTimerIsSane;
static bool s_is_global_timer_sane;
s64 g_globalTimer;
u64 g_fakeTBStartValue;
u64 g_fakeTBStartTicks;
s64 g_global_timer;
u64 g_fake_TB_start_value;
u64 g_fake_TB_start_ticks;
static int ev_lost;
static Event* GetNewEvent()
{
if (!eventPool)
return new Event;
Event* ev = eventPool;
eventPool = ev->next;
return ev;
}
static void FreeEvent(Event* ev)
{
ev->next = eventPool;
eventPool = ev;
}
static EventType* s_ev_lost = nullptr;
static void EmptyTimedCallback(u64 userdata, s64 cyclesLate)
{
@ -96,183 +92,143 @@ static void EmptyTimedCallback(u64 userdata, s64 cyclesLate)
// but the effect is largely the same.
static int DowncountToCycles(int downcount)
{
return (int)(downcount * g_lastOCFactor_inverted);
return static_cast<int>(downcount * g_last_OC_factor_inverted);
}
static int CyclesToDowncount(int cycles)
{
return (int)(cycles * s_lastOCFactor);
return static_cast<int>(cycles * s_last_OC_factor);
}
int RegisterEvent(const std::string& name, TimedCallback callback)
EventType* RegisterEvent(const std::string& name, TimedCallback callback)
{
EventType type;
type.name = name;
type.callback = callback;
// check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization.
for (auto& event_type : event_types)
{
if (name == event_type.name)
{
WARN_LOG(
POWERPC,
"Discarded old event type \"%s\" because a new type with the same name was registered.",
name.c_str());
// we don't know if someone might be holding on to the type index,
// so we gut the old event type instead of actually removing it.
event_type.name = "_discarded_event";
event_type.callback = &EmptyTimedCallback;
}
}
_assert_msg_(POWERPC, s_event_types.find(name) == s_event_types.end(),
"CoreTiming Event \"%s\" is already registered. Events should only be registered "
"during Init to avoid breaking save states.",
name.c_str());
event_types.push_back(type);
return (int)event_types.size() - 1;
auto info = s_event_types.emplace(name, EventType{callback, nullptr});
EventType* event_type = &info.first->second;
event_type->name = &info.first->first;
return event_type;
}
void UnregisterAllEvents()
{
if (first)
PanicAlert("Cannot unregister events with events pending");
event_types.clear();
_assert_msg_(POWERPC, s_event_queue.empty(), "Cannot unregister events with events pending");
s_event_types.clear();
}
void Init()
{
s_lastOCFactor = SConfig::GetInstance().m_OCEnable ? SConfig::GetInstance().m_OCFactor : 1.0f;
g_lastOCFactor_inverted = 1.0f / s_lastOCFactor;
PowerPC::ppcState.downcount = CyclesToDowncount(maxslicelength);
g_slicelength = maxslicelength;
g_globalTimer = 0;
idledCycles = 0;
globalTimerIsSane = true;
s_last_OC_factor = SConfig::GetInstance().m_OCEnable ? SConfig::GetInstance().m_OCFactor : 1.0f;
g_last_OC_factor_inverted = 1.0f / s_last_OC_factor;
PowerPC::ppcState.downcount = CyclesToDowncount(MAX_SLICE_LENGTH);
g_slice_length = MAX_SLICE_LENGTH;
g_global_timer = 0;
s_idled_cycles = 0;
ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
// 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
// executing the first PPC cycle of each slice to prepare the slice length and downcount for
// that slice.
s_is_global_timer_sane = true;
s_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
}
void Shutdown()
{
std::lock_guard<std::mutex> lk(tsWriteLock);
std::lock_guard<std::mutex> lk(s_ts_write_lock);
MoveEvents();
ClearPendingEvents();
UnregisterAllEvents();
while (eventPool)
{
Event* ev = eventPool;
eventPool = ev->next;
delete ev;
}
}
static void EventDoState(PointerWrap& p, BaseEvent* ev)
{
p.Do(ev->time);
// this is why we can't have (nice things) pointers as userdata
p.Do(ev->userdata);
// we can't savestate ev->type directly because events might not get registered in the same order
// (or at all) every time.
// so, we savestate the event's type's name, and derive ev->type from that when loading.
std::string name;
if (p.GetMode() != PointerWrap::MODE_READ)
name = event_types[ev->type].name;
p.Do(name);
if (p.GetMode() == PointerWrap::MODE_READ)
{
bool foundMatch = false;
for (unsigned int i = 0; i < event_types.size(); ++i)
{
if (name == event_types[i].name)
{
ev->type = i;
foundMatch = true;
break;
}
}
if (!foundMatch)
{
WARN_LOG(POWERPC,
"Lost event from savestate because its type, \"%s\", has not been registered.",
name.c_str());
ev->type = ev_lost;
}
}
}
void DoState(PointerWrap& p)
{
std::lock_guard<std::mutex> lk(tsWriteLock);
p.Do(g_slicelength);
p.Do(g_globalTimer);
p.Do(idledCycles);
p.Do(fakeDecStartValue);
p.Do(fakeDecStartTicks);
p.Do(g_fakeTBStartValue);
p.Do(g_fakeTBStartTicks);
p.Do(s_lastOCFactor);
if (p.GetMode() == PointerWrap::MODE_READ)
g_lastOCFactor_inverted = 1.0f / s_lastOCFactor;
std::lock_guard<std::mutex> lk(s_ts_write_lock);
p.Do(g_slice_length);
p.Do(g_global_timer);
p.Do(s_idled_cycles);
p.Do(s_fake_dec_start_value);
p.Do(s_fake_dec_start_ticks);
p.Do(g_fake_TB_start_value);
p.Do(g_fake_TB_start_ticks);
p.Do(s_last_OC_factor);
g_last_OC_factor_inverted = 1.0f / s_last_OC_factor;
p.DoMarker("CoreTimingData");
MoveEvents();
p.DoEachElement(s_event_queue, [](PointerWrap& pw, Event& ev) {
pw.Do(ev.time);
p.DoLinkedList<BaseEvent, GetNewEvent, FreeEvent, EventDoState>(first);
// this is why we can't have (nice things) pointers as userdata
pw.Do(ev.userdata);
// we can't savestate ev.type directly because events might not get registered in the same
// order (or at all) every time.
// so, we savestate the event's type's name, and derive ev.type from that when loading.
std::string name;
if (pw.GetMode() != PointerWrap::MODE_READ)
name = *ev.type->name;
pw.Do(name);
if (pw.GetMode() == PointerWrap::MODE_READ)
{
auto itr = s_event_types.find(name);
if (itr != s_event_types.end())
{
ev.type = &itr->second;
}
else
{
WARN_LOG(POWERPC,
"Lost event from savestate because its type, \"%s\", has not been registered.",
name.c_str());
ev.type = s_ev_lost;
}
}
});
p.DoMarker("CoreTimingEvents");
// When loading from a save state, we must assume the Event order is random and meaningless.
// The exact layout of the heap in memory is implementation defined, therefore it is platform
// and library version specific.
if (p.GetMode() == PointerWrap::MODE_READ)
std::make_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
}
// This should only be called from the CPU thread. If you are calling
// it from any other thread, you are doing something evil
u64 GetTicks()
{
u64 ticks = (u64)g_globalTimer;
if (!globalTimerIsSane)
u64 ticks = static_cast<u64>(g_global_timer);
if (!s_is_global_timer_sane)
{
int downcount = DowncountToCycles(PowerPC::ppcState.downcount);
ticks += g_slicelength - downcount;
ticks += g_slice_length - downcount;
}
return ticks;
}
u64 GetIdleTicks()
{
return (u64)idledCycles;
return static_cast<u64>(s_idled_cycles);
}
void ClearPendingEvents()
{
while (first)
{
Event* e = first->next;
FreeEvent(first);
first = e;
}
s_event_queue.clear();
}
static void AddEventToQueue(Event* ne)
void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata, FromThread from)
{
Event* prev = nullptr;
Event** pNext = &first;
for (;;)
{
Event*& next = *pNext;
if (!next || ne->time < next->time)
{
ne->next = next;
next = ne;
break;
}
prev = next;
pNext = &prev->next;
}
}
_assert_msg_(POWERPC, event_type, "Event type is nullptr, will crash now.");
void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata, FromThread from)
{
bool from_cpu_thread;
if (from == FromThread::ANY)
{
@ -287,16 +243,14 @@ void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata, FromThr
if (from_cpu_thread)
{
Event* ne = GetNewEvent();
ne->time = GetTicks() + cycles_into_future;
ne->userdata = userdata;
ne->type = event_type;
s64 timeout = GetTicks() + cycles_into_future;
// If this event needs to be scheduled before the next advance(), force one early
if (!globalTimerIsSane)
if (!s_is_global_timer_sane)
ForceExceptionCheck(cycles_into_future);
AddEventToQueue(ne);
s_event_queue.emplace_back(Event{timeout, userdata, event_type});
std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
}
else
{
@ -304,49 +258,28 @@ void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata, FromThr
{
ERROR_LOG(POWERPC, "Someone scheduled an off-thread \"%s\" event while netplay or "
"movie play/record was active. This is likely to cause a desync.",
event_types[event_type].name.c_str());
event_type->name->c_str());
}
std::lock_guard<std::mutex> lk(tsWriteLock);
Event ne;
ne.time = g_globalTimer + cycles_into_future;
ne.type = event_type;
ne.userdata = userdata;
tsQueue.Push(ne);
std::lock_guard<std::mutex> lk(s_ts_write_lock);
s_ts_queue.Push(Event{g_global_timer + cycles_into_future, userdata, event_type});
}
}
void RemoveEvent(int event_type)
void RemoveEvent(EventType* event_type)
{
while (first && first->type == event_type)
{
Event* next = first->next;
FreeEvent(first);
first = next;
}
auto itr = std::remove_if(s_event_queue.begin(), s_event_queue.end(),
[&](const Event& e) { return e.type == event_type; });
if (!first)
return;
Event* prev = first;
Event* ptr = prev->next;
while (ptr)
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != s_event_queue.end())
{
if (ptr->type == event_type)
{
prev->next = ptr->next;
FreeEvent(ptr);
ptr = prev->next;
}
else
{
prev = ptr;
ptr = ptr->next;
}
s_event_queue.erase(itr, s_event_queue.end());
std::make_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
}
}
void RemoveAllEvents(int event_type)
void RemoveAllEvents(EventType* event_type)
{
MoveEvents();
RemoveEvent(event_type);
@ -354,26 +287,22 @@ void RemoveAllEvents(int event_type)
void ForceExceptionCheck(s64 cycles)
{
if (s64(DowncountToCycles(PowerPC::ppcState.downcount)) > cycles)
cycles = std::max<s64>(0, cycles);
if (DowncountToCycles(PowerPC::ppcState.downcount) > cycles)
{
// downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int here.
g_slicelength -=
(DowncountToCycles(PowerPC::ppcState.downcount) -
(int)cycles); // Account for cycles already executed by adjusting the g_slicelength
PowerPC::ppcState.downcount = CyclesToDowncount((int)cycles);
// Account for cycles already executed by adjusting the g_slice_length
g_slice_length -= DowncountToCycles(PowerPC::ppcState.downcount) - static_cast<int>(cycles);
PowerPC::ppcState.downcount = CyclesToDowncount(static_cast<int>(cycles));
}
}
void MoveEvents()
{
BaseEvent sevt;
while (tsQueue.Pop(sevt))
for (Event ev; s_ts_queue.Pop(ev);)
{
Event* evt = GetNewEvent();
evt->time = sevt.time;
evt->userdata = sevt.userdata;
evt->type = sevt.type;
AddEventToQueue(evt);
s_event_queue.emplace_back(std::move(ev));
std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
}
}
@ -381,35 +310,34 @@ void Advance()
{
MoveEvents();
int cyclesExecuted = g_slicelength - DowncountToCycles(PowerPC::ppcState.downcount);
g_globalTimer += cyclesExecuted;
s_lastOCFactor = SConfig::GetInstance().m_OCEnable ? SConfig::GetInstance().m_OCFactor : 1.0f;
g_lastOCFactor_inverted = 1.0f / s_lastOCFactor;
g_slicelength = maxslicelength;
int cyclesExecuted = g_slice_length - DowncountToCycles(PowerPC::ppcState.downcount);
g_global_timer += cyclesExecuted;
s_last_OC_factor = SConfig::GetInstance().m_OCEnable ? SConfig::GetInstance().m_OCFactor : 1.0f;
g_last_OC_factor_inverted = 1.0f / s_last_OC_factor;
g_slice_length = MAX_SLICE_LENGTH;
globalTimerIsSane = true;
s_is_global_timer_sane = true;
while (first && first->time <= g_globalTimer)
while (!s_event_queue.empty() && s_event_queue.front().time <= g_global_timer)
{
// LOG(POWERPC, "[Scheduler] %s (%lld, %lld) ",
// event_types[first->type].name ? event_types[first->type].name : "?",
// (u64)g_globalTimer, (u64)first->time);
Event* evt = first;
first = first->next;
event_types[evt->type].callback(evt->userdata, (int)(g_globalTimer - evt->time));
FreeEvent(evt);
Event evt = std::move(s_event_queue.front());
std::pop_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
s_event_queue.pop_back();
// NOTICE_LOG(POWERPC, "[Scheduler] %-20s (%lld, %lld)", evt.type->name->c_str(),
// g_global_timer, evt.time);
evt.type->callback(evt.userdata, g_global_timer - evt.time);
}
globalTimerIsSane = false;
s_is_global_timer_sane = false;
if (first)
// Still events left (scheduled in the future)
if (!s_event_queue.empty())
{
g_slicelength = (int)(first->time - g_globalTimer);
if (g_slicelength > maxslicelength)
g_slicelength = maxslicelength;
g_slice_length = static_cast<int>(
std::min<s64>(s_event_queue.front().time - g_global_timer, MAX_SLICE_LENGTH));
}
PowerPC::ppcState.downcount = CyclesToDowncount(g_slicelength);
PowerPC::ppcState.downcount = CyclesToDowncount(g_slice_length);
// Check for any external exceptions.
// It's important to do this after processing events otherwise any exceptions will be delayed
@ -420,12 +348,12 @@ void Advance()
void LogPendingEvents()
{
Event* ptr = first;
while (ptr)
auto clone = s_event_queue;
std::sort(clone.begin(), clone.end());
for (const Event& ev : clone)
{
INFO_LOG(POWERPC, "PENDING: Now: %" PRId64 " Pending: %" PRId64 " Type: %d", g_globalTimer,
ptr->time, ptr->type);
ptr = ptr->next;
INFO_LOG(POWERPC, "PENDING: Now: %" PRId64 " Pending: %" PRId64 " Type: %s", g_global_timer,
ev.time, ev.type->name->c_str());
}
}
@ -439,68 +367,63 @@ void Idle()
Fifo::FlushGpu();
}
idledCycles += DowncountToCycles(PowerPC::ppcState.downcount);
s_idled_cycles += DowncountToCycles(PowerPC::ppcState.downcount);
PowerPC::ppcState.downcount = 0;
}
std::string GetScheduledEventsSummary()
{
Event* ptr = first;
std::string text = "Scheduled events\n";
text.reserve(1000);
while (ptr)
auto clone = s_event_queue;
std::sort(clone.begin(), clone.end());
for (const Event& ev : clone)
{
unsigned int t = ptr->type;
if (t >= event_types.size())
PanicAlertT("Invalid event type %i", t);
const std::string& name = event_types[ptr->type].name;
text += StringFromFormat("%s : %" PRIi64 " %016" PRIx64 "\n", name.c_str(), ptr->time,
ptr->userdata);
ptr = ptr->next;
text += StringFromFormat("%s : %" PRIi64 " %016" PRIx64 "\n", ev.type->name->c_str(), ev.time,
ev.userdata);
}
return text;
}
u32 GetFakeDecStartValue()
{
return fakeDecStartValue;
return s_fake_dec_start_value;
}
void SetFakeDecStartValue(u32 val)
{
fakeDecStartValue = val;
s_fake_dec_start_value = val;
}
u64 GetFakeDecStartTicks()
{
return fakeDecStartTicks;
return s_fake_dec_start_ticks;
}
void SetFakeDecStartTicks(u64 val)
{
fakeDecStartTicks = val;
s_fake_dec_start_ticks = val;
}
u64 GetFakeTBStartValue()
{
return g_fakeTBStartValue;
return g_fake_TB_start_value;
}
void SetFakeTBStartValue(u64 val)
{
g_fakeTBStartValue = val;
g_fake_TB_start_value = val;
}
u64 GetFakeTBStartTicks()
{
return g_fakeTBStartTicks;
return g_fake_TB_start_ticks;
}
void SetFakeTBStartTicks(u64 val)
{
g_fakeTBStartTicks = val;
g_fake_TB_start_ticks = val;
}
} // namespace

View File

@ -25,12 +25,14 @@ class PointerWrap;
namespace CoreTiming
{
// These really shouldn't be global, but jit64 accesses them directly
extern s64 g_globalTimer;
extern u64 g_fakeTBStartValue;
extern u64 g_fakeTBStartTicks;
extern int g_slicelength;
extern float g_lastOCFactor_inverted;
extern s64 g_global_timer;
extern u64 g_fake_TB_start_value;
extern u64 g_fake_TB_start_ticks;
extern int g_slice_length;
extern float g_last_OC_factor_inverted;
// 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.
void Init();
void Shutdown();
@ -43,9 +45,11 @@ u64 GetIdleTicks();
void DoState(PointerWrap& p);
struct EventType;
// Returns the event_type identifier. if name is not unique, an existing event_type will be
// discarded.
int RegisterEvent(const std::string& name, TimedCallback callback);
EventType* RegisterEvent(const std::string& name, TimedCallback callback);
void UnregisterAllEvents();
enum class FromThread
@ -58,12 +62,22 @@ enum class FromThread
};
// userdata MAY NOT CONTAIN POINTERS. userdata might get written and reloaded from savestates.
void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata = 0,
// After the first Advance, the slice lengths and the downcount will be reduced whenever an event
// is scheduled earlier than the current values (when scheduled from the CPU Thread only).
// Scheduling from a callback will not update the downcount until the Advance() completes.
void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata = 0,
FromThread from = FromThread::CPU);
// We only permit one event of each type in the queue at a time.
void RemoveEvent(int event_type);
void RemoveAllEvents(int event_type);
void RemoveEvent(EventType* event_type);
void RemoveAllEvents(EventType* event_type);
// Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends
// the previous timing slice and begins the next one, you must Advance from the previous
// slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an
// Advance() is required to initialize the slice length before the first cycle of emulated
// instructions is executed.
// NOTE: Advance updates the PowerPC downcount and performs a PPC external exception check.
void Advance();
void MoveEvents();

View File

@ -130,7 +130,7 @@ static void GenerateAudioInterrupt();
static void UpdateInterrupts();
static void IncreaseSampleCount(const u32 _uAmount);
static int GetAIPeriod();
static int et_AI;
static CoreTiming::EventType* et_AI;
static void Update(u64 userdata, s64 cyclesLate);
void Init()

View File

@ -188,8 +188,8 @@ static void UpdateInterrupts();
static void Do_ARAM_DMA();
static void GenerateDSPInterrupt(u64 DSPIntType, s64 cyclesLate = 0);
static int et_GenerateDSPInterrupt;
static int et_CompleteARAM;
static CoreTiming::EventType* et_GenerateDSPInterrupt;
static CoreTiming::EventType* et_CompleteARAM;
static void CompleteARAM(u64 userdata, s64 cyclesLate)
{

View File

@ -238,14 +238,14 @@ static u32 s_error_code = 0;
static bool s_disc_inside = false;
static bool s_stream = false;
static bool s_stop_at_track_end = false;
static int s_finish_executing_command = 0;
static int s_dtk = 0;
static CoreTiming::EventType* s_finish_executing_command;
static CoreTiming::EventType* s_dtk;
static u64 s_last_read_offset;
static u64 s_last_read_time;
static int s_eject_disc;
static int s_insert_disc;
static CoreTiming::EventType* s_eject_disc;
static CoreTiming::EventType* s_insert_disc;
static void EjectDiscCallback(u64 userdata, s64 cyclesLate);
static void InsertDiscCallback(u64 userdata, s64 cyclesLate);

View File

@ -30,7 +30,7 @@ namespace DVDThread
static void DVDThread();
static void FinishRead(u64 userdata, s64 cycles_late);
static int s_finish_read;
static CoreTiming::EventType* s_finish_read;
static std::thread s_dvd_thread;
static Common::Event s_dvd_thread_start_working;

View File

@ -12,6 +12,7 @@
#include "Core/CoreTiming.h"
#include "Core/HW/EXI.h"
#include "Core/HW/EXI_Channel.h"
#include "Core/HW/EXI_DeviceMemoryCard.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/Sram.h"
@ -23,8 +24,8 @@ bool g_SRAM_netplay_initialized = false;
namespace ExpansionInterface
{
static int changeDevice;
static int updateInterrupts;
static CoreTiming::EventType* changeDevice;
static CoreTiming::EventType* updateInterrupts;
static std::array<std::unique_ptr<CEXIChannel>, MAX_EXI_CHANNELS> g_Channels;
@ -38,6 +39,7 @@ void Init()
InitSRAM();
}
CEXIMemoryCard::Init();
for (u32 i = 0; i < MAX_EXI_CHANNELS; i++)
g_Channels[i] = std::make_unique<CEXIChannel>(i);
@ -65,6 +67,8 @@ void Shutdown()
{
for (auto& channel : g_Channels)
channel.reset();
CEXIMemoryCard::Shutdown();
}
void DoState(PointerWrap& p)

View File

@ -2,6 +2,7 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <array>
#include <cstring>
#include <memory>
#include <string>
@ -41,6 +42,9 @@
static const u32 MC_TRANSFER_RATE_READ = 512 * 1024;
static const u32 MC_TRANSFER_RATE_WRITE = (u32)(96.125f * 1024.0f);
static std::array<CoreTiming::EventType*, 2> s_et_cmd_done;
static std::array<CoreTiming::EventType*, 2> s_et_transfer_complete;
// Takes care of the nasty recovery of the 'this' pointer from card_index,
// stored in the userdata parameter of the CoreTiming event.
void CEXIMemoryCard::EventCompleteFindInstance(u64 userdata,
@ -70,25 +74,37 @@ void CEXIMemoryCard::TransferCompleteCallback(u64 userdata, s64 cyclesLate)
[](CEXIMemoryCard* instance) { instance->TransferComplete(); });
}
void CEXIMemoryCard::Init()
{
static constexpr char DONE_PREFIX[] = "memcardDone";
static constexpr char TRANSFER_COMPLETE_PREFIX[] = "memcardTransferComplete";
static_assert(s_et_cmd_done.size() == s_et_transfer_complete.size(), "Event array size differs");
for (unsigned int i = 0; i < s_et_cmd_done.size(); ++i)
{
std::string name = DONE_PREFIX;
name += static_cast<char>('A' + i);
s_et_cmd_done[i] = CoreTiming::RegisterEvent(name, CmdDoneCallback);
name = TRANSFER_COMPLETE_PREFIX;
name += static_cast<char>('A' + i);
s_et_transfer_complete[i] = CoreTiming::RegisterEvent(name, TransferCompleteCallback);
}
}
void CEXIMemoryCard::Shutdown()
{
s_et_cmd_done.fill(nullptr);
s_et_transfer_complete.fill(nullptr);
}
CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder) : card_index(index)
{
struct
{
const char* done;
const char* transfer_complete;
} const event_names[] = {
{"memcardDoneA", "memcardTransferCompleteA"}, {"memcardDoneB", "memcardTransferCompleteB"},
};
_assert_msg_(EXPANSIONINTERFACE, static_cast<std::size_t>(index) < s_et_cmd_done.size(),
"Trying to create invalid memory card index %d.", index);
if ((size_t)index >= ArraySize(event_names))
{
PanicAlertT("Trying to create invalid memory card index.");
}
// we're potentially leaking events here, since there's no RemoveEvent
// until emu shutdown, but I guess it's inconsequential
et_cmd_done = CoreTiming::RegisterEvent(event_names[index].done, CmdDoneCallback);
et_transfer_complete =
CoreTiming::RegisterEvent(event_names[index].transfer_complete, TransferCompleteCallback);
// NOTE: When loading a save state, DMA completion callbacks (s_et_transfer_complete) and such
// may have been restored, we need to anticipate those arriving.
interruptSwitch = 0;
m_bInterruptSet = 0;
@ -248,8 +264,8 @@ void CEXIMemoryCard::SetupRawMemcard(u16 sizeMb)
CEXIMemoryCard::~CEXIMemoryCard()
{
CoreTiming::RemoveEvent(et_cmd_done);
CoreTiming::RemoveEvent(et_transfer_complete);
CoreTiming::RemoveEvent(s_et_cmd_done[card_index]);
CoreTiming::RemoveEvent(s_et_transfer_complete[card_index]);
}
bool CEXIMemoryCard::UseDelayedTransferCompletion() const
@ -279,8 +295,8 @@ void CEXIMemoryCard::TransferComplete()
void CEXIMemoryCard::CmdDoneLater(u64 cycles)
{
CoreTiming::RemoveEvent(et_cmd_done);
CoreTiming::ScheduleEvent((int)cycles, et_cmd_done, (u64)card_index);
CoreTiming::RemoveEvent(s_et_cmd_done[card_index]);
CoreTiming::ScheduleEvent((int)cycles, s_et_cmd_done[card_index], (u64)card_index);
}
void CEXIMemoryCard::SetCS(int cs)
@ -547,7 +563,7 @@ void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize)
// Schedule transfer complete later based on read speed
CoreTiming::ScheduleEvent(_uSize * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_READ),
et_transfer_complete, (u64)card_index);
s_et_transfer_complete[card_index], (u64)card_index);
}
// DMA write are preceded by all of the necessary setup via IMMWrite
@ -563,5 +579,5 @@ void CEXIMemoryCard::DMAWrite(u32 _uAddr, u32 _uSize)
// Schedule transfer complete later based on write speed
CoreTiming::ScheduleEvent(_uSize * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE),
et_transfer_complete, (u64)card_index);
s_et_transfer_complete[card_index], (u64)card_index);
}

View File

@ -26,6 +26,12 @@ public:
void DMARead(u32 _uAddr, u32 _uSize) override;
void DMAWrite(u32 _uAddr, u32 _uSize) override;
// CoreTiming events need to be registered during boot since CoreTiming is DoState()-ed
// before ExpansionInterface so we'll lose the save stated events if the callbacks are
// not already registered first.
static void Init();
static void Shutdown();
private:
void SetupGciFolder(u16 sizeMb);
void SetupRawMemcard(u16 sizeMb);
@ -67,7 +73,6 @@ private:
};
int card_index;
int et_cmd_done, et_transfer_complete;
//! memory card state
// STATE_TO_SAVE

View File

@ -31,10 +31,10 @@ static u32 m_FlipperRev;
static u32 m_Unknown;
// ID and callback for scheduling reset button presses/releases
static int toggleResetButton;
static CoreTiming::EventType* toggleResetButton;
static void ToggleResetButtonCallback(u64 userdata, s64 cyclesLate);
static int iosNotifyResetButton;
static CoreTiming::EventType* iosNotifyResetButton;
static void IOSNotifyResetButtonCallback(u64 userdata, s64 cyclesLate);
// Let the PPC know that an external exception is set/cleared

View File

@ -23,8 +23,8 @@
namespace SerialInterface
{
static int changeDevice;
static int et_transfer_pending;
static CoreTiming::EventType* changeDevice;
static CoreTiming::EventType* et_transfer_pending;
static void RunSIBuffer(u64 userdata, s64 cyclesLate);
static void UpdateInterrupts();

View File

@ -64,13 +64,14 @@ IPC_HLE_PERIOD: For the Wiimote this is the call schedule:
namespace SystemTimers
{
static int et_Dec;
static int et_VI;
static int et_AudioDMA;
static int et_DSP;
static int et_IPC_HLE;
static int et_PatchEngine; // PatchEngine updates every 1/60th of a second by default
static int et_Throttle;
static CoreTiming::EventType* et_Dec;
static CoreTiming::EventType* et_VI;
static CoreTiming::EventType* et_AudioDMA;
static CoreTiming::EventType* et_DSP;
static CoreTiming::EventType* et_IPC_HLE;
// PatchEngine updates every 1/60th of a second by default
static CoreTiming::EventType* et_PatchEngine;
static CoreTiming::EventType* et_Throttle;
static u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!)

View File

@ -98,7 +98,7 @@ static u32 arm_irq_masks;
static u32 sensorbar_power; // do we need to care about this?
static int updateInterrupts;
static CoreTiming::EventType* updateInterrupts;
static void UpdateInterrupts(u64 = 0, s64 cyclesLate = 0);
void DoState(PointerWrap& p)
@ -113,7 +113,7 @@ void DoState(PointerWrap& p)
p.Do(sensorbar_power);
}
void Init()
static void InitState()
{
ctrl = CtrlRegister();
ppc_msg = 0;
@ -127,14 +127,18 @@ void Init()
sensorbar_power = 0;
ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY;
}
void Init()
{
InitState();
updateInterrupts = CoreTiming::RegisterEvent("IPCInterrupt", UpdateInterrupts);
}
void Reset()
{
INFO_LOG(WII_IPC, "Resetting ...");
Init();
InitState();
WII_IPC_HLE_Interface::Reset();
}

View File

@ -76,8 +76,8 @@ static ipc_msg_queue request_queue; // ppc -> arm
static ipc_msg_queue reply_queue; // arm -> ppc
static ipc_msg_queue ack_queue; // arm -> ppc
static int event_enqueue;
static int event_sdio_notify;
static CoreTiming::EventType* event_enqueue;
static CoreTiming::EventType* event_sdio_notify;
static u64 last_reply_time;

View File

@ -15,7 +15,7 @@
#include "Core/MemoryWatcher.h"
static std::unique_ptr<MemoryWatcher> s_memory_watcher;
static int s_event;
static CoreTiming::EventType* s_event;
static const int MW_RATE = 600; // Steps per second
static void MWCallback(u64 userdata, s64 cyclesLate)

View File

@ -198,7 +198,7 @@ void Interpreter::SingleStep()
{
SingleStepInner();
CoreTiming::g_slicelength = 1;
CoreTiming::g_slice_length = 1;
PowerPC::ppcState.downcount = 0;
CoreTiming::Advance();

View File

@ -286,13 +286,13 @@ void Jit64::mfspr(UGeckoInstruction inst)
// cost of calling out to C for this is actually significant.
// Scale downcount by the CPU overclocking factor.
CVTSI2SS(XMM0, PPCSTATE(downcount));
MULSS(XMM0, M(&CoreTiming::g_lastOCFactor_inverted));
MULSS(XMM0, M(&CoreTiming::g_last_OC_factor_inverted));
CVTSS2SI(RDX, R(XMM0)); // RDX is downcount scaled by the overclocking factor
MOV(32, R(RAX), M(&CoreTiming::g_slicelength));
MOV(32, R(RAX), M(&CoreTiming::g_slice_length));
SUB(64, R(RAX), R(RDX)); // cycles since the last CoreTiming::Advance() event is (slicelength -
// Scaled_downcount)
ADD(64, R(RAX), M(&CoreTiming::g_globalTimer));
SUB(64, R(RAX), M(&CoreTiming::g_fakeTBStartTicks));
ADD(64, R(RAX), M(&CoreTiming::g_global_timer));
SUB(64, R(RAX), M(&CoreTiming::g_fake_TB_start_ticks));
// It might seem convenient to correct the timer for the block position here for even more
// accurate
// timing, but as of currently, this can break games. If we end up reading a time *after* the
@ -308,7 +308,7 @@ void Jit64::mfspr(UGeckoInstruction inst)
// a / 12 = (a * 0xAAAAAAAAAAAAAAAB) >> 67
MOV(64, R(RDX), Imm64(0xAAAAAAAAAAAAAAABULL));
MUL(64, R(RDX));
MOV(64, R(RAX), M(&CoreTiming::g_fakeTBStartValue));
MOV(64, R(RAX), M(&CoreTiming::g_fake_TB_start_value));
SHR(64, R(RDX), Imm8(3));
ADD(64, R(RAX), R(RDX));
MOV(64, PPCSTATE(spr[SPR_TL]), R(RAX));

View File

@ -234,9 +234,9 @@ void JitArm64::mfspr(UGeckoInstruction inst)
// An inline implementation of CoreTiming::GetFakeTimeBase, since in timer-heavy games the
// cost of calling out to C for this is actually significant.
MOVI2R(XA, (u64)&CoreTiming::g_globalTimer);
MOVI2R(XA, (u64)&CoreTiming::g_global_timer);
LDR(INDEX_UNSIGNED, XA, XA, 0);
MOVI2R(XB, (u64)&CoreTiming::g_fakeTBStartTicks);
MOVI2R(XB, (u64)&CoreTiming::g_fake_TB_start_ticks);
LDR(INDEX_UNSIGNED, XB, XB, 0);
SUB(XA, XA, XB);
@ -254,7 +254,7 @@ void JitArm64::mfspr(UGeckoInstruction inst)
ADD(XB, XB, 1);
UMULH(XA, XA, XB);
MOVI2R(XB, (u64)&CoreTiming::g_fakeTBStartValue);
MOVI2R(XB, (u64)&CoreTiming::g_fake_TB_start_value);
LDR(INDEX_UNSIGNED, XB, XB, 0);
ADD(XA, XB, XA, ArithOption(XA, ST_LSR, 3));
STR(INDEX_UNSIGNED, XA, PPC_REG, PPCSTATE_OFF(spr[SPR_TL]));

View File

@ -36,7 +36,7 @@ BreakPoints breakpoints;
MemChecks memchecks;
PPCDebugInterface debug_interface;
static int s_invalidate_cache_thread_safe;
static CoreTiming::EventType* s_invalidate_cache_thread_safe;
static void InvalidateCacheThreadSafe(u64 userdata, s64 cyclesLate)
{
ppcState.iCache.Invalidate(static_cast<u32>(userdata));

View File

@ -70,7 +70,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 55;
static const u32 STATE_VERSION = 56;
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,

View File

@ -142,7 +142,6 @@ bool VideoSoftware::Initialize(void* window_handle)
SWOGLWindow::Init(window_handle);
PixelEngine::Init();
Clipper::Init();
Rasterizer::Init();
SWRenderer::Init();

View File

@ -21,7 +21,7 @@
namespace CommandProcessor
{
static int et_UpdateInterrupts;
static CoreTiming::EventType* et_UpdateInterrupts;
// TODO(ector): Warn on bbox read/write

View File

@ -48,7 +48,7 @@ static u8* s_fifo_aux_read_ptr;
static bool s_use_deterministic_gpu_thread;
static u64 s_last_sync_gpu_tick;
static int s_event_sync_gpu;
static CoreTiming::EventType* s_event_sync_gpu;
// STATE_TO_SAVE
static u8* s_video_buffer;

View File

@ -100,7 +100,7 @@ static bool s_event_raised;
static bool s_signal_token_interrupt;
static bool s_signal_finish_interrupt;
static int et_SetTokenFinishOnMainThread;
static CoreTiming::EventType* et_SetTokenFinishOnMainThread;
enum
{

View File

@ -1,2 +1,3 @@
add_dolphin_test(MMIOTest MMIOTest.cpp)
add_dolphin_test(PageFaultTest PageFaultTest.cpp)
add_dolphin_test(CoreTimingTest CoreTimingTest.cpp)

View File

@ -0,0 +1,320 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <gtest/gtest.h>
#include <array>
#include <bitset>
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/PowerPC/PowerPC.h"
// Numbers are chosen randomly to make sure the correct one is given.
static constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
static constexpr int MAX_SLICE_LENGTH = 20000; // Copied from CoreTiming internals
static std::bitset<CB_IDS.size()> s_callbacks_ran_flags;
static u64 s_expected_callback = 0;
static s64 s_lateness = 0;
template <unsigned int IDX>
void CallbackTemplate(u64 userdata, s64 lateness)
{
static_assert(IDX < CB_IDS.size(), "IDX out of range");
s_callbacks_ran_flags.set(IDX);
EXPECT_EQ(CB_IDS[IDX], userdata);
if (s_expected_callback) // In SharedSlot, we don't care about this
EXPECT_EQ(CB_IDS[IDX], s_expected_callback);
EXPECT_EQ(s_lateness, lateness);
}
class ScopeInit final
{
public:
ScopeInit()
{
Core::DeclareAsCPUThread();
SConfig::Init();
PowerPC::Init(PowerPC::CORE_INTERPRETER);
CoreTiming::Init();
}
~ScopeInit()
{
CoreTiming::Shutdown();
PowerPC::Shutdown();
SConfig::Shutdown();
Core::UndeclareAsCPUThread();
}
};
void AdvanceAndCheck(u32 idx, int downcount, int expected_lateness = 0, int cpu_downcount = 0)
{
s_callbacks_ran_flags = 0;
s_expected_callback = CB_IDS[idx];
s_lateness = expected_lateness;
PowerPC::ppcState.downcount = cpu_downcount; // Pretend we executed X cycles of instructions.
CoreTiming::Advance();
EXPECT_EQ(decltype(s_callbacks_ran_flags)().set(idx), s_callbacks_ran_flags);
EXPECT_EQ(downcount, PowerPC::ppcState.downcount);
}
TEST(CoreTiming, BasicOrder)
{
ScopeInit guard;
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>);
CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", CallbackTemplate<3>);
CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", CallbackTemplate<4>);
// Enter slice 0
CoreTiming::Advance();
// D -> B -> C -> A -> E
CoreTiming::ScheduleEvent(1000, cb_a, CB_IDS[0]);
EXPECT_EQ(1000, PowerPC::ppcState.downcount);
CoreTiming::ScheduleEvent(500, cb_b, CB_IDS[1]);
EXPECT_EQ(500, PowerPC::ppcState.downcount);
CoreTiming::ScheduleEvent(800, cb_c, CB_IDS[2]);
EXPECT_EQ(500, PowerPC::ppcState.downcount);
CoreTiming::ScheduleEvent(100, cb_d, CB_IDS[3]);
EXPECT_EQ(100, PowerPC::ppcState.downcount);
CoreTiming::ScheduleEvent(1200, cb_e, CB_IDS[4]);
EXPECT_EQ(100, PowerPC::ppcState.downcount);
AdvanceAndCheck(3, 400);
AdvanceAndCheck(1, 300);
AdvanceAndCheck(2, 200);
AdvanceAndCheck(0, 200);
AdvanceAndCheck(4, MAX_SLICE_LENGTH);
}
TEST(CoreTiming, SharedSlot)
{
ScopeInit guard;
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>);
CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", CallbackTemplate<3>);
CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", CallbackTemplate<4>);
CoreTiming::ScheduleEvent(1000, cb_a, CB_IDS[0]);
CoreTiming::ScheduleEvent(1000, cb_b, CB_IDS[1]);
CoreTiming::ScheduleEvent(1000, cb_c, CB_IDS[2]);
CoreTiming::ScheduleEvent(1000, cb_d, CB_IDS[3]);
CoreTiming::ScheduleEvent(1000, cb_e, CB_IDS[4]);
// Enter slice 0
CoreTiming::Advance();
EXPECT_EQ(1000, PowerPC::ppcState.downcount);
s_callbacks_ran_flags = 0;
s_lateness = 0;
s_expected_callback = 0;
PowerPC::ppcState.downcount = 0;
CoreTiming::Advance();
EXPECT_EQ(MAX_SLICE_LENGTH, PowerPC::ppcState.downcount);
EXPECT_EQ(0x1FULL, s_callbacks_ran_flags.to_ullong());
}
TEST(CoreTiming, PredictableLateness)
{
ScopeInit guard;
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
// Enter slice 0
CoreTiming::Advance();
CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]);
CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]);
AdvanceAndCheck(0, 90, 10, -10); // (100 - 10)
AdvanceAndCheck(1, MAX_SLICE_LENGTH, 50, -50);
}
namespace ChainSchedulingTest
{
static int s_reschedules = 0;
static void RescheduleCallback(u64 userdata, s64 lateness)
{
--s_reschedules;
EXPECT_TRUE(s_reschedules >= 0);
EXPECT_EQ(s_lateness, lateness);
if (s_reschedules > 0)
CoreTiming::ScheduleEvent(1000, reinterpret_cast<CoreTiming::EventType*>(userdata), userdata);
}
}
TEST(CoreTiming, ChainScheduling)
{
using namespace ChainSchedulingTest;
ScopeInit guard;
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>);
CoreTiming::EventType* cb_rs =
CoreTiming::RegisterEvent("callbackReschedule", RescheduleCallback);
// Enter slice 0
CoreTiming::Advance();
CoreTiming::ScheduleEvent(800, cb_a, CB_IDS[0]);
CoreTiming::ScheduleEvent(1000, cb_b, CB_IDS[1]);
CoreTiming::ScheduleEvent(2200, cb_c, CB_IDS[2]);
CoreTiming::ScheduleEvent(1000, cb_rs, reinterpret_cast<u64>(cb_rs));
EXPECT_EQ(800, PowerPC::ppcState.downcount);
s_reschedules = 3;
AdvanceAndCheck(0, 200); // cb_a
AdvanceAndCheck(1, 1000); // cb_b, cb_rs
EXPECT_EQ(2, s_reschedules);
PowerPC::ppcState.downcount = 0;
CoreTiming::Advance(); // cb_rs
EXPECT_EQ(1, s_reschedules);
EXPECT_EQ(200, PowerPC::ppcState.downcount);
AdvanceAndCheck(2, 800); // cb_c
PowerPC::ppcState.downcount = 0;
CoreTiming::Advance(); // cb_rs
EXPECT_EQ(0, s_reschedules);
EXPECT_EQ(MAX_SLICE_LENGTH, PowerPC::ppcState.downcount);
}
namespace ScheduleIntoPastTest
{
static CoreTiming::EventType* s_cb_next = nullptr;
static void ChainCallback(u64 userdata, s64 lateness)
{
EXPECT_EQ(CB_IDS[0] + 1, userdata);
EXPECT_EQ(0, lateness);
CoreTiming::ScheduleEvent(-1000, s_cb_next, userdata - 1);
}
}
// This can happen when scheduling from outside the CPU Thread.
// Also, if the callback is very late, it may reschedule itself for the next period which
// is also in the past.
TEST(CoreTiming, ScheduleIntoPast)
{
using namespace ScheduleIntoPastTest;
ScopeInit guard;
s_cb_next = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
CoreTiming::EventType* cb_chain = CoreTiming::RegisterEvent("callbackChain", ChainCallback);
// Enter slice 0
CoreTiming::Advance();
CoreTiming::ScheduleEvent(1000, cb_chain, CB_IDS[0] + 1);
EXPECT_EQ(1000, PowerPC::ppcState.downcount);
AdvanceAndCheck(0, MAX_SLICE_LENGTH, 1000); // Run cb_chain into late cb_a
// Schedule late from wrong thread
// The problem with scheduling CPU events from outside the CPU Thread is that g_global_timer
// is not reliable outside the CPU Thread. It's possible for the other thread to sample the
// global timer right before the timer is updated by Advance() then submit a new event using
// 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.
Core::UndeclareAsCPUThread();
CoreTiming::g_global_timer -= 1000;
CoreTiming::ScheduleEvent(0, cb_b, CB_IDS[1], CoreTiming::FromThread::NON_CPU);
CoreTiming::g_global_timer += 1000;
Core::DeclareAsCPUThread();
AdvanceAndCheck(1, MAX_SLICE_LENGTH, MAX_SLICE_LENGTH + 1000);
// Schedule directly into the past from the CPU.
// This shouldn't happen in practice, but it's best if we don't mess up the slice length and
// downcount if we do.
CoreTiming::ScheduleEvent(-1000, s_cb_next, CB_IDS[0]);
EXPECT_EQ(0, PowerPC::ppcState.downcount);
AdvanceAndCheck(0, MAX_SLICE_LENGTH, 1000);
}
TEST(CoreTiming, Overclocking)
{
ScopeInit guard;
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>);
CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", CallbackTemplate<3>);
CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", CallbackTemplate<4>);
// Overclock
SConfig::GetInstance().m_OCEnable = true;
SConfig::GetInstance().m_OCFactor = 2.0;
// Enter slice 0
// Updates s_last_OC_factor.
CoreTiming::Advance();
CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]);
CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]);
CoreTiming::ScheduleEvent(400, cb_c, CB_IDS[2]);
CoreTiming::ScheduleEvent(800, cb_d, CB_IDS[3]);
CoreTiming::ScheduleEvent(1600, cb_e, CB_IDS[4]);
EXPECT_EQ(200, PowerPC::ppcState.downcount);
AdvanceAndCheck(0, 200); // (200 - 100) * 2
AdvanceAndCheck(1, 400); // (400 - 200) * 2
AdvanceAndCheck(2, 800); // (800 - 400) * 2
AdvanceAndCheck(3, 1600); // (1600 - 800) * 2
AdvanceAndCheck(4, MAX_SLICE_LENGTH * 2);
// Underclock
SConfig::GetInstance().m_OCFactor = 0.5;
CoreTiming::Advance();
CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]);
CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]);
CoreTiming::ScheduleEvent(400, cb_c, CB_IDS[2]);
CoreTiming::ScheduleEvent(800, cb_d, CB_IDS[3]);
CoreTiming::ScheduleEvent(1600, cb_e, CB_IDS[4]);
EXPECT_EQ(50, PowerPC::ppcState.downcount);
AdvanceAndCheck(0, 50); // (200 - 100) / 2
AdvanceAndCheck(1, 100); // (400 - 200) / 2
AdvanceAndCheck(2, 200); // (800 - 400) / 2
AdvanceAndCheck(3, 400); // (1600 - 800) / 2
AdvanceAndCheck(4, MAX_SLICE_LENGTH / 2);
// Try switching the clock mid-emulation
SConfig::GetInstance().m_OCFactor = 1.0;
CoreTiming::Advance();
CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]);
CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]);
CoreTiming::ScheduleEvent(400, cb_c, CB_IDS[2]);
CoreTiming::ScheduleEvent(800, cb_d, CB_IDS[3]);
CoreTiming::ScheduleEvent(1600, cb_e, CB_IDS[4]);
EXPECT_EQ(100, PowerPC::ppcState.downcount);
AdvanceAndCheck(0, 100); // (200 - 100)
SConfig::GetInstance().m_OCFactor = 2.0;
AdvanceAndCheck(1, 400); // (400 - 200) * 2
AdvanceAndCheck(2, 800); // (800 - 400) * 2
SConfig::GetInstance().m_OCFactor = 0.1f;
AdvanceAndCheck(3, 80); // (1600 - 800) / 10
SConfig::GetInstance().m_OCFactor = 1.0;
AdvanceAndCheck(4, MAX_SLICE_LENGTH);
}