Attempt to calculate actual refresh rate (i.e. a CPU-GPU synced Mhz), no real success. Anybody have any ideas?

Is there no indication from the game when the screen refresh should occur? No, not what I could find, we currently calculate the refresh rate and m_VBeamPos from the CPU ticks progress. That works perfectly if the CPU and GPU is perfectly synced as in the single core and no-idle skipping mode. So I guess it's possible that the game doesn't indicate when the screen should be refreshed, but rather that the hardware calculate that from the CPU ticks progress. That leaves us with a problem in the dual core and idle skipping modes to calculate a CPU-GPU synced CPU ticks progress.

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@3447 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
John Peterson 2009-06-15 04:30:02 +00:00
parent 3295ec38eb
commit 5c04af50a4
12 changed files with 242 additions and 78 deletions

View File

@ -94,12 +94,14 @@ void CMixer::PushSamples(short *samples, int num_stereo_samples, int core_sample
if (!m_throttle)
return;
// -----------------------------------------------------------------------
// The auto throttle function. This loop will put a ceiling on the CPU MHz.
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
/* This is only needed for non-AX sound, currently directly
streamed and DTK sound. For AX we call SoundStream::Update in
AXTask() for example. */
while (m_queueSize > queue_maxlength / 2) {
while (m_queueSize > queue_maxlength / 2)
{
// Urgh.
if (g_dspInitialize.pEmulatorState) {
if (*g_dspInitialize.pEmulatorState != 0)
@ -108,6 +110,7 @@ void CMixer::PushSamples(short *samples, int num_stereo_samples, int core_sample
soundStream->Update();
Common::SleepCurrentThread(0);
}
// -----------------------------------------------------------------------
push_sync.Enter();
while (num_stereo_samples) {

View File

@ -111,6 +111,17 @@ void MatrixMul(int n, const float *a, const float *b, float *result)
}
}
// Calculate sum of a float list
float MathFloatVectorSum(std::vector<float> Vec)
{
float Sum = 0.0;
for(int i = 0; i < Vec.size(); i++)
{
Sum += Vec.at(i);
}
return Sum;
}
void Matrix33::LoadIdentity(Matrix33 &mtx)
{
memset(mtx.data, 0, sizeof(mtx.data));

View File

@ -121,6 +121,7 @@ inline double pow2(double x) {return x * x;}
void SaveSSEState();
void LoadSSEState();
void LoadDefaultSSEState();
float MathFloatVectorSum(std::vector<float>);
#define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))

View File

@ -29,6 +29,7 @@
#include "Timer.h"
#include "Common.h"
#include "StringUtil.h"
#include "MathUtil.h"
#include "Console.h"
#include "Core.h"
@ -672,61 +673,143 @@ void Callback_VideoCopiedToXFB()
frames++;
// -----------------------------------------------------------------------
// Custom frame limiter
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
if (targetfps > 0)
{
new_frametime = Timer.GetTimeDifference() - old_frametime;
old_frametime = Timer.GetTimeDifference();
wait_frametime = (1000/targetfps) - (u16)new_frametime;
if (targetfps < 35)
wait_frametime--;
if (wait_frametime > 0)
Common::SleepCurrentThread(wait_frametime*2);
}
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Is it possible to calculate the CPU-GPU synced ticks for the dual core mode too?
// And possible the idle skipping mode too?
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
static int Diff = 0, DistOld = 0;
Diff = CommandProcessor::fifo.CPReadWriteDistance - DistOld;
// If the CPReadWriteDistance has increased since the last frame we assume the CPU has raced
// ahead of the GPU and we adjust the ticks. Why multiply the difference with 700? I don't know,
// please fix it if possible.
if (Diff > 0) VideoInterface::SyncTicksProgress -= Diff * 700;
DistOld = CommandProcessor::fifo.CPReadWriteDistance;
// -----------------------------------------------------------------------
if (Timer.GetTimeDifference() >= 1000)
{
old_frametime=0;
u64 newTicks = CoreTiming::GetTicks();
u64 newIdleTicks = CoreTiming::GetIdleTicks();
s64 diff = (newTicks - ticks) / 1000000;
s64 idleDiff = (newIdleTicks - idleTicks) / 1000000;
ticks = newTicks;
idleTicks = newIdleTicks;
// Time passed
float t = (float)(Timer.GetTimeDifference()) / 1000.f;
char temp[256];
sprintf(temp, "FPS:%8.2f - Core: %s | %s - Speed: %i MHz [Real: %i + IdleSkip: %i] / %i MHz",
(float)frames / t,
#if defined(JITTEST) && JITTEST
#ifdef _M_IX86
_CoreParameter.bUseJIT ? "JIT32IL" : "Int32",
#else
_CoreParameter.bUseJIT ? "JIT64IL" : "Int64",
#endif
#else
#ifdef _M_IX86
_CoreParameter.bUseJIT ? "JIT32" : "Int32",
#else
_CoreParameter.bUseJIT ? "JIT64" : "Int64",
#endif
#endif
_CoreParameter.bUseDualCore ? "DC" : "SC",
(int)(diff),
(int)(diff - idleDiff),
(int)(idleDiff),
SystemTimers::GetTicksPerSecond() / 1000000);
// Use extended or summary information. The summary information does not print the ticks data,
// that's more of a debugging interest, it can always be optional of course if someone is interested.
//#define EXTENDED_INFO
#ifdef EXTENDED_INFO
u64 newTicks = CoreTiming::GetTicks();
u64 newIdleTicks = CoreTiming::GetIdleTicks();
s64 diff = (newTicks - ticks) / 1000000;
s64 idleDiff = (newIdleTicks - idleTicks) / 1000000;
ticks = newTicks;
idleTicks = newIdleTicks;
float TicksPercentage = (float)diff / (float)(SystemTimers::GetTicksPerSecond() / 1000000) * 100;
#endif
float FPS = (float)frames / t;
float FPS_To_VPS_Rate = ((float)FPS / VideoInterface::ActualRefreshRate);
// ---------------------------------------------------------------------
// For the sake of the dual core mode calculate an average to somewhat reduce the variations
// in the FPS/VPS rate
// ______________________________
/**/
if (_CoreParameter.bUseDualCore)
{
static std::vector <float> FPSVPSList;
static float AverageOver = 5.0;
if (FPSVPSList.size() == AverageOver) FPSVPSList.erase(FPSVPSList.begin());
FPSVPSList.push_back(FPS_To_VPS_Rate);
if (FPSVPSList.size() == AverageOver)
FPS_To_VPS_Rate = MathFloatVectorSum(FPSVPSList) / AverageOver;
}
// ---------------------------------------------------------------------
// Correct the FPS/VPS rate for temporary CPU-GPU timing variations. This rate can only be 1/Integer
// so we set it to either 0.33, 0.5 or 1.0 depending on which it's closest to.
/*
1. Notice: This rate can currently not be calculated with any accuracy at all when idle skipping
is on (the suggested tick rate in that case is much to high in proportion to the actual FPS)
2. When dual core is enabled the CommandProcessor allow some elasticity in the FPS/VPS rate. This
is especially noticable in Zelda TP who's FPS/VPS for me varies between 0.25 and 0.6 as a
result of this.
3. PAL 50Hz games: Are 'patched' so that they still run at the correct speed. So if the NTSC 60Hz
version has a FPS/VPS of 0.5 the 50Hz game will run at 0.6.
*/
// ______________________________
/**/
if (FPS_To_VPS_Rate > 0 && FPS_To_VPS_Rate < ((1.0/3.0 + 1.0/2.0)/2)) FPS_To_VPS_Rate = 1.0/3.0;
else if (FPS_To_VPS_Rate > ((1.0/3.0 + 1.0/2.0)/2) && FPS_To_VPS_Rate < ((1.0/2.0 + 1.0/1.0)/2)) FPS_To_VPS_Rate = 1.0/2.0;
else FPS_To_VPS_Rate = 1.0;
// PAL patch adjustment
if (VideoInterface::TargetRefreshRate == 50) FPS_To_VPS_Rate = FPS_To_VPS_Rate * 1.2;
// ---------------------------------------------------------------------
float TargetFPS = FPS_To_VPS_Rate * (float)VideoInterface::TargetRefreshRate;
float FPSPercentage = (FPS / TargetFPS) * 100.0;
float VPSPercentage = (VideoInterface::ActualRefreshRate / (float)VideoInterface::TargetRefreshRate) * 100.0;
// Settings are shown the same for both extended and summary info
std::string SSettings = StringFromFormat(" | Core: %s %s",
#if defined(JITTEST) && JITTEST
#ifdef _M_IX86
_CoreParameter.bUseJIT ? "JIT32IL" : "Int32",
#else
_CoreParameter.bUseJIT ? "JIT64IL" : "Int64",
#endif
#else
#ifdef _M_IX86
_CoreParameter.bUseJIT ? "JIT32" : "Int32",
#else
_CoreParameter.bUseJIT ? "JIT64" : "Int64",
#endif
#endif
_CoreParameter.bUseDualCore ? "DC" : "SC");
// Show ~ to indicate that the value may be wrong because the ticks are wrong
std::string IdleSkipMessage = "";
if (_CoreParameter.bSkipIdle || _CoreParameter.bUseDualCore) IdleSkipMessage = "~";
#ifdef EXTENDED_INFO
std::string SFPS = StringFromFormat("FPS: %4.1f/%s%2.0f (%s%3.0f%% | %s%1.2f) VPS:%4.0f/%i (%3.0f%%)",
FPS, IdleSkipMessage.c_str(), TargetFPS,
IdleSkipMessage.c_str(), FPSPercentage, IdleSkipMessage.c_str(), FPS_To_VPS_Rate,
VideoInterface::ActualRefreshRate, VideoInterface::TargetRefreshRate, VPSPercentage);
std::string STicks = StringFromFormat(" | CPU: %s%i MHz [Real: %i + IdleSkip: %i] / %i MHz (%s%3.0f%%)",
IdleSkipMessage.c_str(),
(int)(diff),
(int)(diff - idleDiff),
(int)(idleDiff),
SystemTimers::GetTicksPerSecond() / 1000000,
IdleSkipMessage.c_str(),
TicksPercentage);
// Summary information
#else
std::string SFPS = StringFromFormat("FPS: %4.1f/%s%2.0f (%s%3.0f%%)",
FPS, IdleSkipMessage.c_str(), TargetFPS, IdleSkipMessage.c_str(), FPSPercentage);
std::string STicks = "";
#endif
std::string SMessage = StringFromFormat("%s%s%s", SFPS.c_str(), SSettings.c_str(), STicks.c_str());
// Show message
if (g_pUpdateFPSDisplay != NULL)
g_pUpdateFPSDisplay(temp);
Host_UpdateStatusBar(temp);
g_pUpdateFPSDisplay(SMessage.c_str());
Host_UpdateStatusBar(SMessage.c_str());
// Reset frame counter
frames = 0;
Timer.Update();
}

View File

@ -22,6 +22,7 @@
#include "CoreTiming.h"
#include "StringUtil.h"
#include <mmsystem.h>
namespace CoreTiming
{

View File

@ -641,7 +641,7 @@ void STACKALIGN GatherPipeBursted()
}
// This is mostly used in single core mode
void CatchUpGPU()
{
// check if we are able to run this buffer

View File

@ -258,7 +258,7 @@ void SetToken(const u16 _token, const int _bSetTokenAcknowledge)
}
// SetFinish
// THIS IS EXECUTED FROM VIDEO THREAD
// THIS IS EXECUTED FROM VIDEO THREAD (BPStructs.cpp) when a new frame has been drawn
void SetFinish()
{
CommandProcessor::IncrementGPWDToken(); // for DC watchdog hack

View File

@ -28,6 +28,9 @@
#include "../PluginManager.h"
#include "../CoreTiming.h"
#include "../HW/SystemTimers.h"
#include "StringUtil.h"
#include <mmsystem.h>
namespace VideoInterface
{
@ -336,7 +339,7 @@ static u32 LineCount = 0;
static u32 LinesPerField = 0;
static u64 LastTime = 0;
static u32 NextXFBRender = 0;
int TargetRefreshRate = 0, SyncTicksProgress = 0; float ActualRefreshRate = 0.0;
void DoState(PointerWrap &p)
{
@ -965,6 +968,7 @@ void UpdateInterrupts()
}
}
// This function is unused
void GenerateVIInterrupt(VIInterruptType _VIInterrupt)
{
switch(_VIInterrupt)
@ -1001,6 +1005,10 @@ u8* GetXFBPointerBottom()
return Memory::GetPointer(m_XFBInfoBottom.FBB);
}
//////////////////////////////////////////////////////////////////////////////////////////
// Screenshot and screen message
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
void UpdateTiming()
{
switch (m_DisplayControlRegister.FMT)
@ -1027,9 +1035,42 @@ void UpdateTiming()
break;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// Purpose 1: Send VI interrupt for every screen refresh
// Purpose 2: Execute XFB copy in homebrew games
// Run when: This is run 7200 times per second on full speed
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
void Update()
{
// -----------------------------------------------------------------------
// Calculate actual refresh rate
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Update the target refresh rate
TargetRefreshRate = (m_DisplayControlRegister.FMT == 0 || m_DisplayControlRegister.FMT == 2)
? 60 : 50;
// Calculate actual refresh rate
static u64 LastTick = 0;
static int UpdateCheck = timeGetTime() + 1000, TickProgress = 0;
if (UpdateCheck < timeGetTime())
{
UpdateCheck = timeGetTime() + 1000;
TickProgress = CoreTiming::GetTicks() - LastTick;
// Calculated CPU-GPU synced ticks for the dual core mode too
NOTICE_LOG(VIDEO, "Removed: %s Mhz", ThS(SyncTicksProgress / 1000000, false).c_str());
SyncTicksProgress += TickProgress;
// Multipled by two because of the way TicksPerFrame is calculated (divided by 25 and 30
// rather than 50 and 60)
ActualRefreshRate = ((float)SyncTicksProgress / (float)TicksPerFrame) * 2.0;
LastTick = CoreTiming::GetTicks();
SyncTicksProgress = 0;
}
// -----------------------------------------------------------------------
// Go through all lines
while ((CoreTiming::GetTicks() - LastTime) > (TicksPerFrame / LineCount))
{
LastTime += (TicksPerFrame / LineCount);
@ -1101,5 +1142,6 @@ void Update()
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
}
} // namespace

View File

@ -52,6 +52,7 @@ namespace VideoInterface
// Update and draw framebuffer(s)
void Update();
extern float ActualRefreshRate; extern int TargetRefreshRate, SyncTicksProgress;
// UpdateInterrupts: check if we have to generate a new VI Interrupt
void UpdateInterrupts();

View File

@ -160,7 +160,8 @@ void BPWritten(const Bypass& bp)
PixelShaderManager::SetDestAlpha(bpmem.dstalpha);
break;
}
case BPMEM_SETDRAWDONE: // This is called when the game is done drawing (eg: like in DX: Begin(); Draw(); End();)
// This is called when the game is done drawing the new frame (eg: like in DX: Begin(); Draw(); End();)
case BPMEM_SETDRAWDONE:
switch (bp.newvalue & 0xFF)
{
case 0x02:

View File

@ -76,26 +76,6 @@ u8* FAKE_GetFifoEndPtr()
return &videoBuffer[size];
}
// The loop in EnterLoop sends data through this function.
// TODO: Possibly inline it? This one is exported so it will likely not be inlined at all.
void Video_SendFifoData(u8* _uData, u32 len)
{
if (size + len >= FIFO_SIZE)
{
int pos = (int)(g_pVideoData - videoBuffer);
if (size - pos > pos)
{
PanicAlert("FIFO out of bounds (sz = %i, at %08x)", size, pos);
}
memmove(&videoBuffer[0], &videoBuffer[pos], size - pos);
size -= pos;
g_pVideoData = FAKE_GetFifoStartPtr();
}
memcpy(videoBuffer + size, _uData, len);
size += len;
OpcodeDecoder_Run();
}
// Executed from another thread, no the graphics thread!
// Basically, all it does is set a flag so that the loop will eventually exit, then
// waits for the event to be set, which happens when the loop does exit.
@ -124,7 +104,35 @@ void Fifo_ExitLoopNonBlocking() {
fifoStateRun = false;
}
//
//////////////////////////////////////////////////////////////////////////////////////////
// Description: Fifo_EnterLoop() sends data through this function.
// TODO: Possibly inline it? This one is exported so it will likely not be inlined at all.
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
void Video_SendFifoData(u8* _uData, u32 len)
{
if (size + len >= FIFO_SIZE)
{
int pos = (int)(g_pVideoData - videoBuffer);
if (size - pos > pos)
{
PanicAlert("FIFO out of bounds (sz = %i, at %08x)", size, pos);
}
memmove(&videoBuffer[0], &videoBuffer[pos], size - pos);
size -= pos;
g_pVideoData = FAKE_GetFifoStartPtr();
}
// Copy new video instructions to videoBuffer for future use in rendering the new picture
memcpy(videoBuffer + size, _uData, len);
size += len;
OpcodeDecoder_Run();
}
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// Description: Main FIFO update loop
// Purpose: Keep the Core HW updated about the CPU-GPU distance
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
void Fifo_EnterLoop(const SVideoInitialize &video_initialize)
{
fifoStateRun = true;
@ -160,7 +168,7 @@ void Fifo_EnterLoop(const SVideoInitialize &video_initialize)
video_initialize.pPeekMessages();
peek_counter = 0;
}
// read the data and send it to the VideoPlugin
// Create pointer to video data and send it to the VideoPlugin
u32 readPtr = _fifo.CPReadPointer;
u8 *uData = video_initialize.pGetMemoryPointer(readPtr);
@ -204,6 +212,7 @@ void Fifo_EnterLoop(const SVideoInitialize &video_initialize)
readPtr += distToSend;
#endif
}
// Execute new instructions found in uData
Video_SendFifoData(uData, distToSend);
Common::SyncInterlockedExchange((LONG*)&_fifo.CPReadPointer, readPtr);
Common::SyncInterlockedExchangeAdd((LONG*)&_fifo.CPReadWriteDistance, -distToSend);
@ -217,3 +226,4 @@ void Fifo_EnterLoop(const SVideoInitialize &video_initialize)
fifo_exit_event.SetTimer();
#endif
}
//////////////////////////////////////////////////////////////////////////////////////////

View File

@ -379,9 +379,10 @@ void Shutdown(void)
OpenGL_Shutdown();
}
// -------------------------------
//////////////////////////////////////////////////////////////////////////////////////////
// Enter and exit the video loop
// -------------------------------
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
void Video_EnterLoop()
{
Fifo_EnterLoop(g_VideoInitialize);
@ -391,13 +392,27 @@ void Video_ExitLoop()
{
Fifo_ExitLoop();
}
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// Screenshot and screen message
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
void Video_Screenshot(const char *_szFilename)
{
Renderer::SetScreenshot(_szFilename);
}
void Video_AddMessage(const char* pstr, u32 milliseconds)
{
OSD::AddMessage(pstr, milliseconds);
}
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// Run from the CPU thread (from VideoInterface.cpp) for certain homebrew games only
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
void Video_UpdateXFB(u8* _pXFB, u32 _dwWidth, u32 _dwHeight, s32 _dwYOffset, bool scheduling)
{
if (g_Config.bUseXFB && XFB_isInit())
@ -422,8 +437,4 @@ void Video_UpdateXFB(u8* _pXFB, u32 _dwWidth, u32 _dwHeight, s32 _dwYOffset, boo
}
}
}
void Video_AddMessage(const char* pstr, u32 milliseconds)
{
OSD::AddMessage(pstr, milliseconds);
}
/////////////////////////////////////////////////////////////////////////////////////////