VI: Implement post-scanout XFB output

This adds about a frame of latency, and since most games don't change
VI registers during scanout, we can get away with outputting the XFB at
the start of scanout. WWE Crush Hour is the (only currently known)
exception, which has flickering problems when doing it this way.

This adds a path to perform the output at the end of scanout, and gates
it behind an option which defaults to using the latency-reducing
pre-scanout path.
This commit is contained in:
Techjar 2021-08-03 18:55:38 -04:00
parent 4b022a4bb1
commit 797d0b7b1b
6 changed files with 30 additions and 13 deletions

View File

@ -143,6 +143,7 @@ const Info<bool> GFX_HACK_DISABLE_COPY_TO_VRAM{{System::GFX, "Hacks", "DisableCo
const Info<bool> GFX_HACK_DEFER_EFB_COPIES{{System::GFX, "Hacks", "DeferEFBCopies"}, true}; const Info<bool> GFX_HACK_DEFER_EFB_COPIES{{System::GFX, "Hacks", "DeferEFBCopies"}, true};
const Info<bool> GFX_HACK_IMMEDIATE_XFB{{System::GFX, "Hacks", "ImmediateXFBEnable"}, false}; const Info<bool> GFX_HACK_IMMEDIATE_XFB{{System::GFX, "Hacks", "ImmediateXFBEnable"}, false};
const Info<bool> GFX_HACK_SKIP_DUPLICATE_XFBS{{System::GFX, "Hacks", "SkipDuplicateXFBs"}, true}; const Info<bool> GFX_HACK_SKIP_DUPLICATE_XFBS{{System::GFX, "Hacks", "SkipDuplicateXFBs"}, true};
const Info<bool> GFX_HACK_EARLY_XFB_OUTPUT{{System::GFX, "Hacks", "EarlyXFBOutput"}, true};
const Info<bool> GFX_HACK_COPY_EFB_SCALED{{System::GFX, "Hacks", "EFBScaledCopy"}, true}; const Info<bool> GFX_HACK_COPY_EFB_SCALED{{System::GFX, "Hacks", "EFBScaledCopy"}, true};
const Info<bool> GFX_HACK_EFB_EMULATE_FORMAT_CHANGES{ const Info<bool> GFX_HACK_EFB_EMULATE_FORMAT_CHANGES{
{System::GFX, "Hacks", "EFBEmulateFormatChanges"}, false}; {System::GFX, "Hacks", "EFBEmulateFormatChanges"}, false};

View File

@ -118,6 +118,7 @@ extern const Info<bool> GFX_HACK_DISABLE_COPY_TO_VRAM;
extern const Info<bool> GFX_HACK_DEFER_EFB_COPIES; extern const Info<bool> GFX_HACK_DEFER_EFB_COPIES;
extern const Info<bool> GFX_HACK_IMMEDIATE_XFB; extern const Info<bool> GFX_HACK_IMMEDIATE_XFB;
extern const Info<bool> GFX_HACK_SKIP_DUPLICATE_XFBS; extern const Info<bool> GFX_HACK_SKIP_DUPLICATE_XFBS;
extern const Info<bool> GFX_HACK_EARLY_XFB_OUTPUT;
extern const Info<bool> GFX_HACK_COPY_EFB_SCALED; extern const Info<bool> GFX_HACK_COPY_EFB_SCALED;
extern const Info<bool> GFX_HACK_EFB_EMULATE_FORMAT_CHANGES; extern const Info<bool> GFX_HACK_EFB_EMULATE_FORMAT_CHANGES;
extern const Info<bool> GFX_HACK_VERTEX_ROUDING; extern const Info<bool> GFX_HACK_VERTEX_ROUDING;

View File

@ -13,6 +13,7 @@
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Core/Config/GraphicsSettings.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/Config/SYSCONFSettings.h" #include "Core/Config/SYSCONFSettings.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
@ -757,7 +758,7 @@ static void LogField(FieldType field, u32 xfb_address)
GetTicksPerOddField()); GetTicksPerOddField());
} }
static void BeginField(FieldType field, u64 ticks) static void OutputField(FieldType field, u64 ticks)
{ {
// Could we fit a second line of data in the stride? // Could we fit a second line of data in the stride?
// (Datel's Wii FreeLoaders are the only titles known to set WPL to 0) // (Datel's Wii FreeLoaders are the only titles known to set WPL to 0)
@ -811,16 +812,30 @@ static void BeginField(FieldType field, u64 ticks)
LogField(field, xfbAddr); LogField(field, xfbAddr);
// This assumes the game isn't going to change the VI registers while a // Outputting the entire frame using a single set of VI register values isn't accurate, as games
// frame is scanning out. // can change the register values during scanout. To correctly emulate the scanout process, we
// To correctly handle that case we would need to collate all changes // would need to collate all changes to the VI registers during scanout.
// to VI during scanout and delay outputting the frame till then.
if (xfbAddr) if (xfbAddr)
g_video_backend->Video_BeginField(xfbAddr, fbWidth, fbStride, fbHeight, ticks); g_video_backend->Video_OutputXFB(xfbAddr, fbWidth, fbStride, fbHeight, ticks);
} }
static void EndField() static void BeginField(FieldType field, u64 ticks)
{ {
// Outputting the frame at the beginning of scanout reduces latency. This assumes the game isn't
// going to change the VI registers while a frame is scanning out.
if (Config::Get(Config::GFX_HACK_EARLY_XFB_OUTPUT))
OutputField(field, ticks);
}
static void EndField(FieldType field, u64 ticks)
{
// If the game does change VI registers while a frame is scanning out, we can defer output
// until the end so the last register values are used. This still isn't accurate, but it does
// produce more acceptable results in some problematic cases.
// Currently, this is only known to be necessary to eliminate flickering in WWE Crush Hour.
if (!Config::Get(Config::GFX_HACK_EARLY_XFB_OUTPUT))
OutputField(field, ticks);
Core::VideoThrottle(); Core::VideoThrottle();
Core::OnFrameEnd(); Core::OnFrameEnd();
} }
@ -849,11 +864,11 @@ void Update(u64 ticks)
} }
else if (s_half_line_count == s_even_field_last_hl) else if (s_half_line_count == s_even_field_last_hl)
{ {
EndField(); EndField(FieldType::Even, ticks);
} }
else if (s_half_line_count == s_odd_field_last_hl) else if (s_half_line_count == s_odd_field_last_hl)
{ {
EndField(); EndField(FieldType::Odd, ticks);
} }
// If this half-line is at a field boundary, deal with frame stepping before potentially // If this half-line is at a field boundary, deal with frame stepping before potentially

View File

@ -373,7 +373,7 @@ void RunGpuLoop()
// This call is pretty important in DualCore mode and must be called in the FIFO Loop. // This call is pretty important in DualCore mode and must be called in the FIFO Loop.
// If we don't, s_swapRequested or s_efbAccessRequested won't be set to false // If we don't, s_swapRequested or s_efbAccessRequested won't be set to false
// leading the CPU thread to wait in Video_BeginField or Video_AccessEFB thus slowing // leading the CPU thread to wait in Video_OutputXFB or Video_AccessEFB thus slowing
// things down. // things down.
AsyncRequests::GetInstance()->PullEvents(); AsyncRequests::GetInstance()->PullEvents();
} }

View File

@ -83,8 +83,8 @@ void VideoBackendBase::Video_ExitLoop()
} }
// Run from the CPU thread (from VideoInterface.cpp) // Run from the CPU thread (from VideoInterface.cpp)
void VideoBackendBase::Video_BeginField(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, void VideoBackendBase::Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height,
u64 ticks) u64 ticks)
{ {
if (m_initialized && g_renderer && !g_ActiveConfig.bImmediateXFB) if (m_initialized && g_renderer && !g_ActiveConfig.bImmediateXFB)
{ {

View File

@ -52,7 +52,7 @@ public:
void Video_ExitLoop(); void Video_ExitLoop();
void Video_BeginField(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks); void Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks);
u32 Video_AccessEFB(EFBAccessType type, u32 x, u32 y, u32 data); u32 Video_AccessEFB(EFBAccessType type, u32 x, u32 y, u32 data);
u32 Video_GetQueryResult(PerfQueryType type); u32 Video_GetQueryResult(PerfQueryType type);