From 2ebbfd6f856d7fad39676568529bc48f9369e103 Mon Sep 17 00:00:00 2001 From: Scott Mansell Date: Sun, 7 Feb 2016 05:59:45 +1300 Subject: [PATCH] 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. --- Source/Core/Core/CoreTiming.cpp | 27 ++++++++++++++++--- Source/Core/Core/CoreTiming.h | 2 ++ .../PowerPC/Jit64/Jit_SystemRegisters.cpp | 8 +++++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index 59d40727ae..7479d03922 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -50,7 +50,7 @@ static Common::FifoQueue 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"); diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index c87cbd55f5..287ea38ccc 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -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 diff --git a/Source/Core/Core/PowerPC/Jit64/Jit_SystemRegisters.cpp b/Source/Core/Core/PowerPC/Jit64/Jit_SystemRegisters.cpp index 8d14610f4b..6669436885 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit_SystemRegisters.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit_SystemRegisters.cpp @@ -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