Adjust cycle counts so they are accurate to the jit block level
Previously GlobalTimer was only updated at the end of each slice when CoreTiming::Advance() was called, so it could be upto 20,000 cycles off. This was causing huge problems with games which made heavy use of the time base register, such as OoT (virtual console) and Pokemon puzzle. I've also made it so event scheduling will be accurate to the jit block level, instead of accurate to the slice.
This commit is contained in:
parent
3ff56aa192
commit
2ebbfd6f85
|
@ -50,7 +50,7 @@ static Common::FifoQueue<BaseEvent, false> tsQueue;
|
|||
// event pools
|
||||
static Event *eventPool = nullptr;
|
||||
|
||||
static float lastOCFactor;
|
||||
float lastOCFactor;
|
||||
int slicelength;
|
||||
static int maxSliceLength = MAX_SLICE_LENGTH;
|
||||
|
||||
|
@ -58,6 +58,9 @@ static s64 idledCycles;
|
|||
static u32 fakeDecStartValue;
|
||||
static u64 fakeDecStartTicks;
|
||||
|
||||
// Are we in a function that has been called from Advance()
|
||||
static bool GlobalTimerIsSane;
|
||||
|
||||
s64 globalTimer;
|
||||
u64 fakeTBStartValue;
|
||||
u64 fakeTBStartTicks;
|
||||
|
@ -137,6 +140,7 @@ void Init()
|
|||
slicelength = maxSliceLength;
|
||||
globalTimer = 0;
|
||||
idledCycles = 0;
|
||||
GlobalTimerIsSane = true;
|
||||
|
||||
ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
|
||||
}
|
||||
|
@ -209,9 +213,16 @@ void DoState(PointerWrap &p)
|
|||
p.DoMarker("CoreTimingEvents");
|
||||
}
|
||||
|
||||
// This should only be called from the CPU thread, if you are calling it any other thread, you are doing something evil
|
||||
u64 GetTicks()
|
||||
{
|
||||
return (u64)globalTimer;
|
||||
u64 ticks = (u64)globalTimer;
|
||||
if (!GlobalTimerIsSane)
|
||||
{
|
||||
int downcount = DowncountToCycles(PowerPC::ppcState.downcount);
|
||||
ticks += slicelength - downcount;
|
||||
}
|
||||
return ticks;
|
||||
}
|
||||
|
||||
u64 GetIdleTicks()
|
||||
|
@ -303,10 +314,16 @@ void ScheduleEvent(int cyclesIntoFuture, int event_type, u64 userdata)
|
|||
{
|
||||
_assert_msg_(POWERPC, Core::IsCPUThread() || Core::GetState() == Core::CORE_PAUSE,
|
||||
"ScheduleEvent from wrong thread");
|
||||
|
||||
Event *ne = GetNewEvent();
|
||||
ne->userdata = userdata;
|
||||
ne->type = event_type;
|
||||
ne->time = globalTimer + cyclesIntoFuture;
|
||||
ne->time = GetTicks() + cyclesIntoFuture;
|
||||
|
||||
// If this event needs to be scheduled before the next advance(), force one early
|
||||
if (!GlobalTimerIsSane)
|
||||
ForceExceptionCheck(cyclesIntoFuture);
|
||||
|
||||
AddEventToQueue(ne);
|
||||
}
|
||||
|
||||
|
@ -402,6 +419,8 @@ void Advance()
|
|||
lastOCFactor = SConfig::GetInstance().m_OCEnable ? SConfig::GetInstance().m_OCFactor : 1.0f;
|
||||
PowerPC::ppcState.downcount = CyclesToDowncount(slicelength);
|
||||
|
||||
GlobalTimerIsSane = true;
|
||||
|
||||
while (first && first->time <= globalTimer)
|
||||
{
|
||||
//LOG(POWERPC, "[Scheduler] %s (%lld, %lld) ",
|
||||
|
@ -412,6 +431,8 @@ void Advance()
|
|||
FreeEvent(evt);
|
||||
}
|
||||
|
||||
GlobalTimerIsSane = false;
|
||||
|
||||
if (!first)
|
||||
{
|
||||
WARN_LOG(POWERPC, "WARNING - no events in queue. Setting downcount to 10000");
|
||||
|
|
|
@ -34,6 +34,7 @@ void Shutdown();
|
|||
|
||||
typedef void (*TimedCallback)(u64 userdata, int cyclesLate);
|
||||
|
||||
// This should only be called from the CPU thread, if you are calling it any other thread, you are doing something evil
|
||||
u64 GetTicks();
|
||||
u64 GetIdleTicks();
|
||||
|
||||
|
@ -79,5 +80,6 @@ void SetFakeTBStartTicks(u64 val);
|
|||
void ForceExceptionCheck(int cycles);
|
||||
|
||||
extern int slicelength;
|
||||
extern float lastOCFactor;
|
||||
|
||||
} // end of namespace
|
||||
|
|
|
@ -283,7 +283,13 @@ void Jit64::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.
|
||||
MOV(64, R(RAX), M(&CoreTiming::globalTimer));
|
||||
// Scale downcount by the CPU overclocking factor.
|
||||
CVTSI2SS(XMM0, PPCSTATE(downcount));
|
||||
DIVSS(XMM0, M(&CoreTiming::lastOCFactor));
|
||||
CVTSS2SI(RDX, R(XMM0)); // RDX is downcount scaled by the overclocking factor
|
||||
MOV(32, R(RAX), M(&CoreTiming::slicelength));
|
||||
SUB(64, R(RAX), R(RDX)); // cycles since the last CoreTiming::Advance() event is (slicelength - Scaled_downcount)
|
||||
ADD(64, R(RAX), M(&CoreTiming::globalTimer));
|
||||
SUB(64, R(RAX), M(&CoreTiming::fakeTBStartTicks));
|
||||
// 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 time
|
||||
|
|
Loading…
Reference in New Issue