Merge pull request #4168 from EmptyChaos/coretiming-cleanup
Core: CoreTiming Cleanup (Add UnitTests)
This commit is contained in:
commit
f5fa5a7d32
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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!)
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -198,7 +198,7 @@ void Interpreter::SingleStep()
|
|||
{
|
||||
SingleStepInner();
|
||||
|
||||
CoreTiming::g_slicelength = 1;
|
||||
CoreTiming::g_slice_length = 1;
|
||||
PowerPC::ppcState.downcount = 0;
|
||||
CoreTiming::Advance();
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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]));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -142,7 +142,6 @@ bool VideoSoftware::Initialize(void* window_handle)
|
|||
|
||||
SWOGLWindow::Init(window_handle);
|
||||
|
||||
PixelEngine::Init();
|
||||
Clipper::Init();
|
||||
Rasterizer::Init();
|
||||
SWRenderer::Init();
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
namespace CommandProcessor
|
||||
{
|
||||
static int et_UpdateInterrupts;
|
||||
static CoreTiming::EventType* et_UpdateInterrupts;
|
||||
|
||||
// TODO(ector): Warn on bbox read/write
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
add_dolphin_test(MMIOTest MMIOTest.cpp)
|
||||
add_dolphin_test(PageFaultTest PageFaultTest.cpp)
|
||||
add_dolphin_test(CoreTimingTest CoreTimingTest.cpp)
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue