diff --git a/Source/Core/Common/Src/PluginVideo.cpp b/Source/Core/Common/Src/PluginVideo.cpp index a17823d718..06c46eb84e 100644 --- a/Source/Core/Common/Src/PluginVideo.cpp +++ b/Source/Core/Common/Src/PluginVideo.cpp @@ -25,6 +25,7 @@ PluginVideo::PluginVideo(const char *_Filename) : CPlugin(_Filename), validVideo Video_Prepare = 0; Video_SendFifoData = 0; Video_BeginField = 0; + Video_EndField = 0; Video_EnterLoop = 0; Video_ExitLoop = 0; Video_Screenshot = 0; @@ -37,6 +38,8 @@ PluginVideo::PluginVideo(const char *_Filename) : CPlugin(_Filename), validVideo (LoadSymbol("Video_SendFifoData")); Video_BeginField = reinterpret_cast (LoadSymbol("Video_BeginField")); + Video_EndField = reinterpret_cast + (LoadSymbol("Video_EndField")); Video_Screenshot = reinterpret_cast (LoadSymbol("Video_Screenshot")); Video_EnterLoop = reinterpret_cast @@ -50,7 +53,8 @@ PluginVideo::PluginVideo(const char *_Filename) : CPlugin(_Filename), validVideo if ((Video_Prepare != 0) && (Video_SendFifoData != 0) && - (Video_BeginField != 0) && + (Video_BeginField != 0) && + (Video_EndField != 0) && (Video_EnterLoop != 0) && (Video_ExitLoop != 0) && (Video_Screenshot != 0) && diff --git a/Source/Core/Common/Src/PluginVideo.h b/Source/Core/Common/Src/PluginVideo.h index ac3335abdf..96005cbaa1 100644 --- a/Source/Core/Common/Src/PluginVideo.h +++ b/Source/Core/Common/Src/PluginVideo.h @@ -26,6 +26,7 @@ namespace Common { typedef void (__cdecl* TVideo_Prepare)(); typedef void (__cdecl* TVideo_SendFifoData)(u8*,u32); typedef void (__cdecl* TVideo_BeginField)(u32, FieldType, u32, u32); +typedef void (__cdecl* TVideo_EndField)(); typedef bool (__cdecl* TVideo_Screenshot)(const char* filename); typedef void (__cdecl* TVideo_EnterLoop)(); typedef void (__cdecl* TVideo_ExitLoop)(); @@ -44,6 +45,7 @@ public: TVideo_EnterLoop Video_EnterLoop; TVideo_ExitLoop Video_ExitLoop; TVideo_BeginField Video_BeginField; + TVideo_EndField Video_EndField; TVideo_AccessEFB Video_AccessEFB; TVideo_AddMessage Video_AddMessage; diff --git a/Source/Core/Core/Src/HW/VideoInterface.cpp b/Source/Core/Core/Src/HW/VideoInterface.cpp index e950413c4a..40e3cb7a64 100644 --- a/Source/Core/Core/Src/HW/VideoInterface.cpp +++ b/Source/Core/Core/Src/HW/VideoInterface.cpp @@ -334,12 +334,15 @@ static UVIBorderBlankRegister m_BorderHBlank; static u32 TicksPerFrame = 0; -static u32 LineCount = 0; -static u32 LinesPerField = 0; -static u32 NextXFBRender = 0; -int TargetRefreshRate = 0; +static u32 s_lineCount = 0; +static u32 s_upperFieldBegin = 0; +static u32 s_upperFieldEnd = 0; +static u32 s_lowerFieldBegin = 0; +static u32 s_lowerFieldEnd = 0; + +double TargetRefreshRate = 0.0; +double ActualRefreshRate = 0.0; s64 SyncTicksProgress = 0; -float ActualRefreshRate = 0.0; void DoState(PointerWrap &p) { @@ -369,15 +372,21 @@ void DoState(PointerWrap &p) p.Do(m_BorderHBlank); p.Do(TicksPerFrame); - p.Do(LineCount); - p.Do(LinesPerField); - p.Do(NextXFBRender); + p.Do(s_lineCount); + p.Do(s_upperFieldBegin); + p.Do(s_upperFieldEnd); + p.Do(s_lowerFieldBegin); + p.Do(s_lowerFieldEnd); } void PreInit(bool _bNTSC) { TicksPerFrame = 0; - LineCount = 0; + s_lineCount = 0; + s_upperFieldBegin = 0; + s_upperFieldEnd = 0; + s_lowerFieldBegin = 0; + s_lowerFieldEnd = 0; m_VerticalTimingRegister.EQU = 6; @@ -437,7 +446,11 @@ void Init() 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) @@ -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 void UpdateTiming() { switch (m_DisplayControlRegister.FMT) { + case 0: // NTSC - case 2: // MPAL - TicksPerFrame = SystemTimers::GetTicksPerSecond() / 30; - LineCount = m_DisplayControlRegister.NIN ? 263 : 525; - LinesPerField = 263; + case 2: // PAL-M + + TicksPerFrame = (u32)(SystemTimers::GetTicksPerSecond() / (NTSC_FIELD_RATE / 2.0)); + 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; case 1: // PAL - TicksPerFrame = SystemTimers::GetTicksPerSecond() / 25; - LineCount = m_DisplayControlRegister.NIN ? 313 : 625; - LinesPerField = 313; + + TicksPerFrame = (u32)(SystemTimers::GetTicksPerSecond() / (PAL_FIELD_RATE / 2.0)); + 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; case 3: // Debug @@ -1031,17 +1083,18 @@ void UpdateTiming() default: PanicAlert("Unknown Video Format - CVideoInterface"); break; + } } int getTicksPerLine() { - if (LineCount == 0) + if (s_lineCount == 0) 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" }; 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 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(); 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 2: Execute XFB copy in homebrew games @@ -1067,7 +1135,7 @@ void Update() { // Update the target refresh rate TargetRefreshRate = (m_DisplayControlRegister.FMT == 0 || m_DisplayControlRegister.FMT == 2) - ? 60 : 50; + ? NTSC_FIELD_RATE : PAL_FIELD_RATE; // Calculate actual refresh rate static u64 LastTick = 0; @@ -1083,36 +1151,12 @@ void Update() // rather than 50 and 60) // 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(); SyncTicksProgress = 0; } - - m_VBeamPos++; - if (m_VBeamPos > LineCount) - { - m_VBeamPos = 1; - } - if (m_VBeamPos == NextXFBRender) - { - 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); - } - } + // TODO: What's the correct behavior for progressive mode? for (int i = 0; i < 4; ++i) { @@ -1122,6 +1166,22 @@ void Update() 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 diff --git a/Source/Core/Core/Src/HW/VideoInterface.h b/Source/Core/Core/Src/HW/VideoInterface.h index f85928fdf3..7a475b8fd2 100644 --- a/Source/Core/Core/Src/HW/VideoInterface.h +++ b/Source/Core/Core/Src/HW/VideoInterface.h @@ -54,8 +54,8 @@ namespace VideoInterface void Update(); // urgh, ugly externs. - extern float ActualRefreshRate; - extern int TargetRefreshRate; + extern double ActualRefreshRate; + extern double TargetRefreshRate; extern s64 SyncTicksProgress; // UpdateInterrupts: check if we have to generate a new VI Interrupt diff --git a/Source/PluginSpecs/pluginspecs_video.h b/Source/PluginSpecs/pluginspecs_video.h index b928c1c032..b455049c6f 100644 --- a/Source/PluginSpecs/pluginspecs_video.h +++ b/Source/PluginSpecs/pluginspecs_video.h @@ -117,14 +117,23 @@ EXPORT void CALL Video_SendFifoData(u8* _uData, u32 len); // __________________________________________________________________________________________________ // Function: Video_BeginField -// Purpose: When a vertical blank occurs in the VI emulator, this function tells the video plugin -// what the parameters of the upcoming field are. The video plugin should make sure the -// previous field is on the player's display before returning. -// input: vi parameters of the next field +// Purpose: When a field begins in the VI emulator, this function tells the video plugin what the +// parameters of the upcoming field are. The video plugin should make sure the previous +// field is on the player's display before returning. +// input: vi parameters of the upcoming field // output: none // 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 // input: type of access (r/w, z/color, ...), x coord, y coord diff --git a/Source/Plugins/Plugin_VideoDX9/Src/main.cpp b/Source/Plugins/Plugin_VideoDX9/Src/main.cpp index 2067231455..1b5078dd00 100644 --- a/Source/Plugins/Plugin_VideoDX9/Src/main.cpp +++ b/Source/Plugins/Plugin_VideoDX9/Src/main.cpp @@ -285,6 +285,10 @@ void Video_BeginField(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight) D3D::BeginFrame();*/ } +void Video_EndField() +{ +} + void Video_AddMessage(const char* pstr, u32 milliseconds) { Renderer::AddMessage(pstr,milliseconds); diff --git a/Source/Plugins/Plugin_VideoOGL/Src/main.cpp b/Source/Plugins/Plugin_VideoOGL/Src/main.cpp index 286291ca48..1072eac2cc 100644 --- a/Source/Plugins/Plugin_VideoOGL/Src/main.cpp +++ b/Source/Plugins/Plugin_VideoOGL/Src/main.cpp @@ -496,20 +496,27 @@ void Video_BeginField(u32 xfbAddr, FieldType field, u32 fbWidth, u32 fbHeight) { if (s_PluginInitialized) { - if (g_VideoInitialize.bUseDualCore) - s_swapResponseEvent.MsgWait(); - else - VideoFifo_CheckSwapRequest(); + if (s_swapRequested) + { + if (g_VideoInitialize.bUseDualCore) + s_swapResponseEvent.MsgWait(); + else + VideoFifo_CheckSwapRequest(); + } s_beginFieldArgs.xfbAddr = xfbAddr; s_beginFieldArgs.field = field; s_beginFieldArgs.fbWidth = fbWidth; 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 { EFBAccessType type;