Better TV emulation. Seems to fix problem in 50 Hz PAL games where frames go backwards.

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@3811 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
Nolan Check
2009-07-15 22:20:59 +00:00
parent a73dd21ee9
commit fdc2b69143
7 changed files with 146 additions and 60 deletions

View File

@ -25,6 +25,7 @@ PluginVideo::PluginVideo(const char *_Filename) : CPlugin(_Filename), validVideo
Video_Prepare = 0; Video_Prepare = 0;
Video_SendFifoData = 0; Video_SendFifoData = 0;
Video_BeginField = 0; Video_BeginField = 0;
Video_EndField = 0;
Video_EnterLoop = 0; Video_EnterLoop = 0;
Video_ExitLoop = 0; Video_ExitLoop = 0;
Video_Screenshot = 0; Video_Screenshot = 0;
@ -37,6 +38,8 @@ PluginVideo::PluginVideo(const char *_Filename) : CPlugin(_Filename), validVideo
(LoadSymbol("Video_SendFifoData")); (LoadSymbol("Video_SendFifoData"));
Video_BeginField = reinterpret_cast<TVideo_BeginField> Video_BeginField = reinterpret_cast<TVideo_BeginField>
(LoadSymbol("Video_BeginField")); (LoadSymbol("Video_BeginField"));
Video_EndField = reinterpret_cast<TVideo_EndField>
(LoadSymbol("Video_EndField"));
Video_Screenshot = reinterpret_cast<TVideo_Screenshot> Video_Screenshot = reinterpret_cast<TVideo_Screenshot>
(LoadSymbol("Video_Screenshot")); (LoadSymbol("Video_Screenshot"));
Video_EnterLoop = reinterpret_cast<TVideo_EnterLoop> Video_EnterLoop = reinterpret_cast<TVideo_EnterLoop>
@ -50,7 +53,8 @@ PluginVideo::PluginVideo(const char *_Filename) : CPlugin(_Filename), validVideo
if ((Video_Prepare != 0) && if ((Video_Prepare != 0) &&
(Video_SendFifoData != 0) && (Video_SendFifoData != 0) &&
(Video_BeginField != 0) && (Video_BeginField != 0) &&
(Video_EndField != 0) &&
(Video_EnterLoop != 0) && (Video_EnterLoop != 0) &&
(Video_ExitLoop != 0) && (Video_ExitLoop != 0) &&
(Video_Screenshot != 0) && (Video_Screenshot != 0) &&

View File

@ -26,6 +26,7 @@ namespace Common {
typedef void (__cdecl* TVideo_Prepare)(); typedef void (__cdecl* TVideo_Prepare)();
typedef void (__cdecl* TVideo_SendFifoData)(u8*,u32); typedef void (__cdecl* TVideo_SendFifoData)(u8*,u32);
typedef void (__cdecl* TVideo_BeginField)(u32, FieldType, u32, u32); typedef void (__cdecl* TVideo_BeginField)(u32, FieldType, u32, u32);
typedef void (__cdecl* TVideo_EndField)();
typedef bool (__cdecl* TVideo_Screenshot)(const char* filename); typedef bool (__cdecl* TVideo_Screenshot)(const char* filename);
typedef void (__cdecl* TVideo_EnterLoop)(); typedef void (__cdecl* TVideo_EnterLoop)();
typedef void (__cdecl* TVideo_ExitLoop)(); typedef void (__cdecl* TVideo_ExitLoop)();
@ -44,6 +45,7 @@ public:
TVideo_EnterLoop Video_EnterLoop; TVideo_EnterLoop Video_EnterLoop;
TVideo_ExitLoop Video_ExitLoop; TVideo_ExitLoop Video_ExitLoop;
TVideo_BeginField Video_BeginField; TVideo_BeginField Video_BeginField;
TVideo_EndField Video_EndField;
TVideo_AccessEFB Video_AccessEFB; TVideo_AccessEFB Video_AccessEFB;
TVideo_AddMessage Video_AddMessage; TVideo_AddMessage Video_AddMessage;

View File

@ -334,12 +334,15 @@ static UVIBorderBlankRegister m_BorderHBlank;
static u32 TicksPerFrame = 0; static u32 TicksPerFrame = 0;
static u32 LineCount = 0; static u32 s_lineCount = 0;
static u32 LinesPerField = 0; static u32 s_upperFieldBegin = 0;
static u32 NextXFBRender = 0; static u32 s_upperFieldEnd = 0;
int TargetRefreshRate = 0; static u32 s_lowerFieldBegin = 0;
static u32 s_lowerFieldEnd = 0;
double TargetRefreshRate = 0.0;
double ActualRefreshRate = 0.0;
s64 SyncTicksProgress = 0; s64 SyncTicksProgress = 0;
float ActualRefreshRate = 0.0;
void DoState(PointerWrap &p) void DoState(PointerWrap &p)
{ {
@ -369,15 +372,21 @@ void DoState(PointerWrap &p)
p.Do(m_BorderHBlank); p.Do(m_BorderHBlank);
p.Do(TicksPerFrame); p.Do(TicksPerFrame);
p.Do(LineCount); p.Do(s_lineCount);
p.Do(LinesPerField); p.Do(s_upperFieldBegin);
p.Do(NextXFBRender); p.Do(s_upperFieldEnd);
p.Do(s_lowerFieldBegin);
p.Do(s_lowerFieldEnd);
} }
void PreInit(bool _bNTSC) void PreInit(bool _bNTSC)
{ {
TicksPerFrame = 0; TicksPerFrame = 0;
LineCount = 0; s_lineCount = 0;
s_upperFieldBegin = 0;
s_upperFieldEnd = 0;
s_lowerFieldBegin = 0;
s_lowerFieldEnd = 0;
m_VerticalTimingRegister.EQU = 6; m_VerticalTimingRegister.EQU = 6;
@ -437,7 +446,11 @@ void Init()
m_DisplayControlRegister.Hex = 0; m_DisplayControlRegister.Hex = 0;
NextXFBRender = 1; s_lineCount = 0;
s_upperFieldBegin = 0;
s_upperFieldEnd = 0;
s_lowerFieldBegin = 0;
s_lowerFieldEnd = 0;
} }
void Read8(u8& _uReturnValue, const u32 _iAddress) void Read8(u8& _uReturnValue, const u32 _iAddress)
@ -1005,23 +1018,62 @@ u32 GetXFBAddressBottom()
} }
// NTSC is 60 FPS, right?
// Wrong, it's about 59.94 FPS. The NTSC engineers had to slightly lower
// the field rate from 60 FPS when they added color to the standard.
// This was done to prevent analog interference between the video and
// audio signals. PAL has no similar reduction; it is exactly 50 FPS.
const double NTSC_FIELD_RATE = 60.0 / 1.001;
const u32 NTSC_LINE_COUNT = 525;
// An NTSC frame has the lower field first followed by the upper field.
// TODO: Is this true for PAL-M? Is this true for EURGB60?
const u32 NTSC_LOWER_BEGIN = 21;
const u32 NTSC_LOWER_END = 263;
const u32 NTSC_UPPER_BEGIN = 283;
const u32 NTSC_UPPER_END = 525;
const double PAL_FIELD_RATE = 50.0;
const u32 PAL_LINE_COUNT = 625;
// A PAL frame has the upper field first followed by the lower field.
const u32 PAL_UPPER_BEGIN = 23; // TODO: Actually 23.5!
const u32 PAL_UPPER_END = 310;
const u32 PAL_LOWER_BEGIN = 336;
const u32 PAL_LOWER_END = 623; // TODO: Actually 623.5!
// Screenshot and screen message // Screenshot and screen message
void UpdateTiming() void UpdateTiming()
{ {
switch (m_DisplayControlRegister.FMT) switch (m_DisplayControlRegister.FMT)
{ {
case 0: // NTSC case 0: // NTSC
case 2: // MPAL case 2: // PAL-M
TicksPerFrame = SystemTimers::GetTicksPerSecond() / 30;
LineCount = m_DisplayControlRegister.NIN ? 263 : 525; TicksPerFrame = (u32)(SystemTimers::GetTicksPerSecond() / (NTSC_FIELD_RATE / 2.0));
LinesPerField = 263; s_lineCount = m_DisplayControlRegister.NIN ? (NTSC_LINE_COUNT+1)/2 : NTSC_LINE_COUNT;
// TODO: The game may have some control over these parameters (not that it's useful).
s_upperFieldBegin = NTSC_UPPER_BEGIN;
s_upperFieldEnd = NTSC_UPPER_END;
s_lowerFieldBegin = NTSC_LOWER_BEGIN;
s_lowerFieldEnd = NTSC_LOWER_END;
break; break;
case 1: // PAL case 1: // PAL
TicksPerFrame = SystemTimers::GetTicksPerSecond() / 25;
LineCount = m_DisplayControlRegister.NIN ? 313 : 625; TicksPerFrame = (u32)(SystemTimers::GetTicksPerSecond() / (PAL_FIELD_RATE / 2.0));
LinesPerField = 313; s_lineCount = m_DisplayControlRegister.NIN ? (PAL_LINE_COUNT+1)/2 : PAL_LINE_COUNT;
s_upperFieldBegin = PAL_UPPER_BEGIN;
s_upperFieldEnd = PAL_UPPER_END;
s_lowerFieldBegin = PAL_LOWER_BEGIN;
s_lowerFieldEnd = PAL_LOWER_END;
break; break;
case 3: // Debug case 3: // Debug
@ -1031,17 +1083,18 @@ void UpdateTiming()
default: default:
PanicAlert("Unknown Video Format - CVideoInterface"); PanicAlert("Unknown Video Format - CVideoInterface");
break; break;
} }
} }
int getTicksPerLine() { int getTicksPerLine() {
if (LineCount == 0) if (s_lineCount == 0)
return 100000; return 100000;
return TicksPerFrame / LineCount; return TicksPerFrame / s_lineCount;
} }
static void BeginField(u32 xfbAddr, FieldType field) static void BeginField(FieldType field)
{ {
static const char* const fieldTypeNames[] = { "Progressive", "Upper", "Lower" }; static const char* const fieldTypeNames[] = { "Progressive", "Upper", "Lower" };
DEBUG_LOG(VIDEOINTERFACE, "(VI->BeginField): addr: %.08X | FieldSteps %u | FbSteps %u | ACV %u | Field %s", DEBUG_LOG(VIDEOINTERFACE, "(VI->BeginField): addr: %.08X | FieldSteps %u | FbSteps %u | ACV %u | Field %s",
@ -1052,6 +1105,12 @@ static void BeginField(u32 xfbAddr, FieldType field)
u32 fbWidth = m_HorizontalStepping.FieldSteps * 16; u32 fbWidth = m_HorizontalStepping.FieldSteps * 16;
u32 fbHeight = (m_HorizontalStepping.FbSteps / m_HorizontalStepping.FieldSteps) * m_VerticalTimingRegister.ACV; u32 fbHeight = (m_HorizontalStepping.FbSteps / m_HorizontalStepping.FieldSteps) * m_VerticalTimingRegister.ACV;
// TODO: Are the "Bottom Field" and "Top Field" registers actually more
// like "First Field" and "Second Field" registers? There's an important
// difference because NTSC and PAL have opposite field orders.
u32 xfbAddr = (field == FIELD_LOWER) ? GetXFBAddressBottom() : GetXFBAddressTop();
Common::PluginVideo* video = CPluginManager::GetInstance().GetVideo(); Common::PluginVideo* video = CPluginManager::GetInstance().GetVideo();
if (xfbAddr && video->IsValid()) if (xfbAddr && video->IsValid())
{ {
@ -1059,6 +1118,15 @@ static void BeginField(u32 xfbAddr, FieldType field)
} }
} }
static void EndField()
{
Common::PluginVideo* video = CPluginManager::GetInstance().GetVideo();
if (video->IsValid())
{
video->Video_EndField();
}
}
// Purpose 1: Send VI interrupt for every screen refresh // Purpose 1: Send VI interrupt for every screen refresh
// Purpose 2: Execute XFB copy in homebrew games // Purpose 2: Execute XFB copy in homebrew games
@ -1067,7 +1135,7 @@ void Update()
{ {
// Update the target refresh rate // Update the target refresh rate
TargetRefreshRate = (m_DisplayControlRegister.FMT == 0 || m_DisplayControlRegister.FMT == 2) TargetRefreshRate = (m_DisplayControlRegister.FMT == 0 || m_DisplayControlRegister.FMT == 2)
? 60 : 50; ? NTSC_FIELD_RATE : PAL_FIELD_RATE;
// Calculate actual refresh rate // Calculate actual refresh rate
static u64 LastTick = 0; static u64 LastTick = 0;
@ -1083,36 +1151,12 @@ void Update()
// rather than 50 and 60) // rather than 50 and 60)
// TODO : Feed the FPS estimate into Iulius' framelimiter. // TODO : Feed the FPS estimate into Iulius' framelimiter.
ActualRefreshRate = ((float)SyncTicksProgress / (float)TicksPerFrame) * 2.0; ActualRefreshRate = ((double)SyncTicksProgress / (double)TicksPerFrame) * 2.0;
LastTick = CoreTiming::GetTicks(); LastTick = CoreTiming::GetTicks();
SyncTicksProgress = 0; SyncTicksProgress = 0;
} }
m_VBeamPos++;
if (m_VBeamPos > LineCount)
{
m_VBeamPos = 1;
}
if (m_VBeamPos == NextXFBRender) // TODO: What's the correct behavior for progressive mode?
{
if (NextXFBRender == 1)
{
NextXFBRender = LinesPerField;
// TODO: proper VI regs typedef and logic for XFB to work.
// eg. Animal Crossing gc have smth in TFBL.XOF bitfield.
// "XOF - Horizontal Offset of the left-most pixel within the first word of the fetched picture."
u32 xfbAddr = GetXFBAddressTop();
BeginField(xfbAddr, m_DisplayControlRegister.NIN ? FIELD_PROGRESSIVE : FIELD_UPPER);
}
else
{
NextXFBRender = 1;
// Previously checked m_XFBInfoTop.POFF then used m_XFBInfoBottom.FBB, try reverting if there are problems
u32 xfbAddr = GetXFBAddressBottom();
BeginField(xfbAddr, m_DisplayControlRegister.NIN ? FIELD_PROGRESSIVE : FIELD_LOWER);
}
}
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
{ {
@ -1122,6 +1166,22 @@ void Update()
UpdateInterrupts(); UpdateInterrupts();
} }
} }
if (m_VBeamPos == s_upperFieldBegin)
BeginField(m_DisplayControlRegister.NIN ? FIELD_PROGRESSIVE : FIELD_UPPER);
if (m_VBeamPos == s_upperFieldEnd)
EndField();
if (m_VBeamPos == s_lowerFieldBegin)
BeginField(m_DisplayControlRegister.NIN ? FIELD_PROGRESSIVE : FIELD_LOWER);
if (m_VBeamPos == s_lowerFieldEnd)
EndField();
m_VBeamPos++;
if (m_VBeamPos > s_lineCount)
m_VBeamPos = 1;
} }
} // namespace } // namespace

View File

@ -54,8 +54,8 @@ namespace VideoInterface
void Update(); void Update();
// urgh, ugly externs. // urgh, ugly externs.
extern float ActualRefreshRate; extern double ActualRefreshRate;
extern int TargetRefreshRate; extern double TargetRefreshRate;
extern s64 SyncTicksProgress; extern s64 SyncTicksProgress;
// UpdateInterrupts: check if we have to generate a new VI Interrupt // UpdateInterrupts: check if we have to generate a new VI Interrupt

View File

@ -117,14 +117,23 @@ EXPORT void CALL Video_SendFifoData(u8* _uData, u32 len);
// __________________________________________________________________________________________________ // __________________________________________________________________________________________________
// Function: Video_BeginField // Function: Video_BeginField
// Purpose: When a vertical blank occurs in the VI emulator, this function tells the video plugin // Purpose: When a field begins in the VI emulator, this function tells the video plugin what the
// what the parameters of the upcoming field are. The video plugin should make sure the // parameters of the upcoming field are. The video plugin should make sure the previous
// previous field is on the player's display before returning. // field is on the player's display before returning.
// input: vi parameters of the next field // input: vi parameters of the upcoming field
// output: none // output: none
// //
EXPORT void CALL Video_BeginField(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight); EXPORT void CALL Video_BeginField(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight);
// __________________________________________________________________________________________________
// Function: Video_EndField
// Purpose: When a field ends in the VI emulator, this function notifies the video plugin. The video
// has permission to swap the field to the player's display.
// input: none
// output: none
//
EXPORT void CALL Video_EndField();
// __________________________________________________________________________________________________ // __________________________________________________________________________________________________
// Function: Video_AccessEFB // Function: Video_AccessEFB
// input: type of access (r/w, z/color, ...), x coord, y coord // input: type of access (r/w, z/color, ...), x coord, y coord

View File

@ -285,6 +285,10 @@ void Video_BeginField(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight)
D3D::BeginFrame();*/ D3D::BeginFrame();*/
} }
void Video_EndField()
{
}
void Video_AddMessage(const char* pstr, u32 milliseconds) void Video_AddMessage(const char* pstr, u32 milliseconds)
{ {
Renderer::AddMessage(pstr,milliseconds); Renderer::AddMessage(pstr,milliseconds);

View File

@ -496,20 +496,27 @@ void Video_BeginField(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight)
{ {
if (s_PluginInitialized) if (s_PluginInitialized)
{ {
if (g_VideoInitialize.bUseDualCore) if (s_swapRequested)
s_swapResponseEvent.MsgWait(); {
else if (g_VideoInitialize.bUseDualCore)
VideoFifo_CheckSwapRequest(); s_swapResponseEvent.MsgWait();
else
VideoFifo_CheckSwapRequest();
}
s_beginFieldArgs.xfbAddr = xfbAddr; s_beginFieldArgs.xfbAddr = xfbAddr;
s_beginFieldArgs.field = field; s_beginFieldArgs.field = field;
s_beginFieldArgs.fbWidth = fbWidth; s_beginFieldArgs.fbWidth = fbWidth;
s_beginFieldArgs.fbHeight = fbHeight; s_beginFieldArgs.fbHeight = fbHeight;
Common::AtomicStoreRelease(s_swapRequested, TRUE);
} }
} }
// Run from the CPU thread (from VideoInterface.cpp)
void Video_EndField()
{
Common::AtomicStoreRelease(s_swapRequested, TRUE);
}
static volatile struct static volatile struct
{ {
EFBAccessType type; EFBAccessType type;