GS-PCRTC: Add option to enable/disable Anti-Blur code.

For... accuracy?

Also clean up FIELD nonsense in the merge circuit.
Fixed up offset code for interlacing
This commit is contained in:
refractionpcsx2 2022-06-14 23:54:08 +01:00
parent c86e82039a
commit b5807e0788
10 changed files with 85 additions and 48 deletions

View File

@ -105,6 +105,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.integerScaling, "EmuCore/GS", "IntegerScaling", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.integerScaling, "EmuCore/GS", "IntegerScaling", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PCRTCOffsets, "EmuCore/GS", "pcrtc_offsets", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PCRTCOffsets, "EmuCore/GS", "pcrtc_offsets", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PCRTCOverscan, "EmuCore/GS", "pcrtc_overscan", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PCRTCOverscan, "EmuCore/GS", "pcrtc_overscan", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PCRTCAntiBlur, "EmuCore/GS", "pcrtc_antiblur", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.DisableInterlaceOffset, "EmuCore/GS", "disable_interlace_offset", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.DisableInterlaceOffset, "EmuCore/GS", "disable_interlace_offset", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.internalResolutionScreenshots, "EmuCore/GS", "InternalResolutionScreenshots", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.internalResolutionScreenshots, "EmuCore/GS", "InternalResolutionScreenshots", false);
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.zoom, "EmuCore/GS", "Zoom", 100.0f); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.zoom, "EmuCore/GS", "Zoom", 100.0f);

View File

@ -323,6 +323,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1">
<widget class="QCheckBox" name="PCRTCAntiBlur">
<property name="toolTip">
<string>Enables internal Anti-Blur hacks. LEss accurate to PS2 rendering but will make a lot of games look less blurry.</string>
</property>
<property name="text">
<string>Anti-Blur</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@ -434,6 +434,7 @@ struct Pcsx2Config
struct struct
{ {
bool bool
PCRTCAntiBlur : 1,
DisableInterlaceOffset : 1, DisableInterlaceOffset : 1,
PCRTCOffsets : 1, PCRTCOffsets : 1,
PCRTCOverscan : 1, PCRTCOverscan : 1,

View File

@ -1370,6 +1370,7 @@ void GSApp::Init()
m_default_configuration["fxaa"] = "0"; m_default_configuration["fxaa"] = "0";
m_default_configuration["GSDumpCompression"] = "0"; m_default_configuration["GSDumpCompression"] = "0";
m_default_configuration["HWDisableReadbacks"] = "0"; m_default_configuration["HWDisableReadbacks"] = "0";
m_default_configuration["pcrtc_antiblur"] = "1";
m_default_configuration["disable_interlace_offset"] = "0"; m_default_configuration["disable_interlace_offset"] = "0";
m_default_configuration["pcrtc_offsets"] = "0"; m_default_configuration["pcrtc_offsets"] = "0";
m_default_configuration["pcrtc_overscan"] = "0"; m_default_configuration["pcrtc_overscan"] = "0";

View File

@ -377,7 +377,7 @@ void GSDevice::Interlace(const GSVector2i& ds, int field, int mode, float yoffse
if (mode == 0 || mode == 2) // weave or blend if (mode == 0 || mode == 2) // weave or blend
{ {
// weave first // weave first
const int offset = static_cast<int>(yoffset) * (1 - field); const int offset = static_cast<int>(yoffset) * field;
DoInterlace(m_merge, m_weavebob, field, false, GSConfig.DisableInterlaceOffset ? 0 : offset); DoInterlace(m_merge, m_weavebob, field, false, GSConfig.DisableInterlaceOffset ? 0 : offset);
@ -398,7 +398,8 @@ void GSDevice::Interlace(const GSVector2i& ds, int field, int mode, float yoffse
} }
else if (mode == 1) // bob else if (mode == 1) // bob
{ {
DoInterlace(m_merge, m_weavebob, 3, true, yoffset * field); // Field is reversed here as we are countering the bounce.
DoInterlace(m_merge, m_weavebob, 3, true, yoffset * (1-field));
m_current = m_weavebob; m_current = m_weavebob;
} }

View File

@ -95,6 +95,7 @@ bool GSRenderer::Merge(int field)
GSVector2i frame_baseline = {INT_MAX, INT_MAX}; GSVector2i frame_baseline = {INT_MAX, INT_MAX};
GSVector2i display_combined = {0, 0}; GSVector2i display_combined = {0, 0};
bool feedback_merge = m_regs->EXTWRITE.WRITE == 1; bool feedback_merge = m_regs->EXTWRITE.WRITE == 1;
bool display_offset = false;
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
@ -114,6 +115,7 @@ bool GSRenderer::Merge(int field)
frame_baseline.x = std::min(fr[i].left, frame_baseline.x); frame_baseline.x = std::min(fr[i].left, frame_baseline.x);
frame_baseline.y = std::min(fr[i].top, frame_baseline.y); frame_baseline.y = std::min(fr[i].top, frame_baseline.y);
display_offset |= std::abs(display_baseline.y - display_offsets[i].y) == 1;
/*DevCon.Warning("Read offset was X %d(left %d) Y %d(top %d)", display_baseline.x, dr[i].left, display_baseline.y, dr[i].top); /*DevCon.Warning("Read offset was X %d(left %d) Y %d(top %d)", display_baseline.x, dr[i].left, display_baseline.y, dr[i].top);
DevCon.Warning("[%d]: %d %d %d %d, %d %d %d %d\n", i, fr[i].x,fr[i].y,fr[i].z,fr[i].w , dr[i].x,dr[i].y,dr[i].z,dr[i].w); DevCon.Warning("[%d]: %d %d %d %d, %d %d %d %d\n", i, fr[i].x,fr[i].y,fr[i].z,fr[i].w , dr[i].x,dr[i].y,dr[i].z,dr[i].w);
DevCon.Warning("Offset X %d Offset Y %d", display_offsets[i].x, display_offsets[i].y);*/ DevCon.Warning("Offset X %d Offset Y %d", display_offsets[i].x, display_offsets[i].y);*/
@ -168,7 +170,7 @@ bool GSRenderer::Merge(int field)
const bool slbg = m_regs->PMODE.SLBG; const bool slbg = m_regs->PMODE.SLBG;
GSVector2i resolution(GetResolution()); GSVector2i resolution(GetResolution());
bool scanmask_frame = true; bool scanmask_frame = m_scanmask_used && !display_offset;
const bool ignore_offset = !GSConfig.PCRTCOffsets; const bool ignore_offset = !GSConfig.PCRTCOffsets;
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
@ -186,18 +188,25 @@ bool GSRenderer::Merge(int field)
// If using scanmsk we have to keep the single line offset, regardless of upscale // If using scanmsk we have to keep the single line offset, regardless of upscale
// so we handle this separately after the rect calculations. // so we handle this separately after the rect calculations.
const int interlace_offset = display_diff.y & 1; float interlace_offset = 0.0f;
if (m_scanmask_used && interlace_offset) if ((!GSConfig.PCRTCAntiBlur || m_scanmask_used) && display_offset)
{ {
display_diff.y &= ~1; interlace_offset = static_cast<float>(display_diff.y & 1);
scanmask_frame = false;
// When the displays are offset by 1 we need to adjust for upscale to handle it (reduces bounce in MGS2 when upscaling)
interlace_offset += (tex[i]->GetScale().y - 1.0f) / 2;
if (!ignore_offset) if (!ignore_offset)
off.y &= ~1; off.y &= ~1;
display_diff.y &= ~1;
} }
// Start of Anti-Blur code. // Start of Anti-Blur code.
if (!ignore_offset) if (!ignore_offset)
{
if (GSConfig.PCRTCAntiBlur)
{ {
if (samesrc) if (samesrc)
{ {
@ -214,10 +223,22 @@ bool GSRenderer::Merge(int field)
off.y += 1; off.y += 1;
} }
} }
}
else else
{ {
if (!slbg || !feedback_merge) if (!slbg || !feedback_merge)
{ {
// If the offsets between the two displays are quite large, it's probably intended for an effect.
if (display_diff.x >= 4 || !GSConfig.PCRTCAntiBlur)
off.x = display_diff.x;
if (display_diff.y >= 4 || !GSConfig.PCRTCAntiBlur)
off.y = display_diff.y;
if (samesrc)
{
// Adjusting the screen offset when using a negative offset.
const int videomode = static_cast<int>(GetVideoMode()) - 1; const int videomode = static_cast<int>(GetVideoMode()) - 1;
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
GSVector2i base_resolution(offsets.x, offsets.y); GSVector2i base_resolution(offsets.x, offsets.y);
@ -225,16 +246,6 @@ bool GSRenderer::Merge(int field)
if (isinterlaced() && !m_regs->SMODE2.FFMD) if (isinterlaced() && !m_regs->SMODE2.FFMD)
base_resolution.y *= 2; base_resolution.y *= 2;
// If the offsets between the two displays are quite large, it's probably intended for an effect.
if (display_diff.x >= 4)
off.x = display_diff.x;
if (display_diff.y >= 4)
off.y = display_diff.y;
// Anti blur hax.
if (samesrc)
{
// Offset by DISPLAY setting // Offset by DISPLAY setting
if (display_diff.x < 0) if (display_diff.x < 0)
{ {
@ -249,17 +260,21 @@ bool GSRenderer::Merge(int field)
resolution.y -= display_diff.y; resolution.y -= display_diff.y;
} }
// Don't do X, we only care about height, this would need to be tailored for games using X (Black does -5).
// Mainly for Hokuto no Ken which does -14 Y offset.
if (display_baseline.y < -4)
off.y += display_baseline.y;
// Anti-Blur stuff
if (GSConfig.PCRTCAntiBlur)
{
// Offset by DISPFB setting // Offset by DISPFB setting
if (frame_diff.x == 1) if (frame_diff.x == 1)
off.x += 1; off.x += 1;
if (frame_diff.y == 1) if (frame_diff.y == 1)
off.y += 1; off.y += 1;
}
// Don't do X, we only care about height, this would need to be tailored for games using X (Black does -5).
// Mainly for Hokuto no Ken which does -14 Y offset.
if (display_baseline.y < -4)
off.y += display_baseline.y;
} }
} }
} }
@ -277,9 +292,8 @@ bool GSRenderer::Merge(int field)
// src_out_rect is the resized rect for output. (Not really used) // src_out_rect is the resized rect for output. (Not really used)
src_out_rect[i] = (GSVector4(r) * scale) / GSVector4(tex[i]->GetSize()).xyxy(); src_out_rect[i] = (GSVector4(r) * scale) / GSVector4(tex[i]->GetSize()).xyxy();
// Restore the single line offset for scanmsk. // Restore manually offset "interlace" lines
if (m_scanmask_used && interlace_offset) dst[i] += GSVector4(0.0f, interlace_offset, 0.0f, interlace_offset);
dst[i] += GSVector4(0.0f, 1.0f, 0.0f, 1.0f);
} }
if (feedback_merge && tex[2]) if (feedback_merge && tex[2])
@ -335,24 +349,24 @@ bool GSRenderer::Merge(int field)
g_gs_device->Merge(tex, src_gs_read, dst, fs, m_regs->PMODE, m_regs->EXTBUF, c); g_gs_device->Merge(tex, src_gs_read, dst, fs, m_regs->PMODE, m_regs->EXTBUF, c);
// Offset is not compatible with scanmsk, as scanmsk renders every other line, but at x7 the interlace offset will be 7 lines // Use offset for bob deinterlacing, for normal deinterlacing we add an extra 1 since it's clearer, with bob that causes extra shake.
const int offset = (m_scanmask_used || !m_regs->SMODE2.FFMD) ? 0 : (int)(tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y); const bool is_bob = GSConfig.InterlaceMode == GSInterlaceMode::BobTFF || GSConfig.InterlaceMode == GSInterlaceMode::BobBFF;
const float offset = (!m_regs->SMODE2.FFMD && !is_bob) ? 0 : ((tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y));
if (isReallyInterlaced() && GSConfig.InterlaceMode != GSInterlaceMode::Off) if (isReallyInterlaced() && GSConfig.InterlaceMode != GSInterlaceMode::Off)
{ {
const bool scanmask = m_scanmask_used && scanmask_frame && GSConfig.InterlaceMode == GSInterlaceMode::Automatic; // FFMD (half frames) requires blend deinterlacing, so automatically use that. Same when SCANMSK is used but not blended in the merge circuit (Alpine Racer 3)
if (GSConfig.InterlaceMode == GSInterlaceMode::Automatic && (m_regs->SMODE2.FFMD || scanmask_frame))
if (GSConfig.InterlaceMode == GSInterlaceMode::Automatic && (m_regs->SMODE2.FFMD)) // Auto interlace enabled / Odd frame interlace setting
{ {
constexpr int field2 = 1; constexpr int field2 = 0;
constexpr int mode = 2; constexpr int mode = 2;
g_gs_device->Interlace(ds, field ^ field2, mode, offset); g_gs_device->Interlace(ds, field ^ field2, mode, offset);
} }
else else
{ {
const int field2 = scanmask ? 0 : 1 - ((static_cast<int>(GSConfig.InterlaceMode) - 1) & 1); const int field2 = ((static_cast<int>(GSConfig.InterlaceMode) - 1) & 1);
const int mode = scanmask ? 2 : ((static_cast<int>(GSConfig.InterlaceMode) - 1) >> 1); const int mode = ((static_cast<int>(GSConfig.InterlaceMode) - 1) >> 1);
g_gs_device->Interlace(ds, field ^ field2, mode, offset); g_gs_device->Interlace(ds, field ^ field2, mode, offset);
} }

View File

@ -97,6 +97,9 @@ const char* dialog_message(int ID, bool* updateText)
return cvtString("Shows more overscan area for some games which expect there to be some.\n" return cvtString("Shows more overscan area for some games which expect there to be some.\n"
"Used for games like Need for Speed Underground (PAL),Crash Nitro Kart (PAL) \n" "Used for games like Need for Speed Underground (PAL),Crash Nitro Kart (PAL) \n"
"and Espgaluda (When using certain Tate modes)"); "and Espgaluda (When using certain Tate modes)");
case IDC_PCRTC_ANTIBLUR:
return cvtString("Enable: Adds internal anti-blur hacks on the merge ciruit.\n"
"This will likely make a lot of games look crisper but is less accurate to the PS2.");
case IDC_DISABLE_INTERLACE_OFFSETS: case IDC_DISABLE_INTERLACE_OFFSETS:
return cvtString("Enable: Removes the offset for interlacing when upscaling.\n" return cvtString("Enable: Removes the offset for interlacing when upscaling.\n"
"Can reduce blurring in some games, where the opposite is true most of the time.\n" "Can reduce blurring in some games, where the opposite is true most of the time.\n"

View File

@ -44,6 +44,7 @@ enum
IDC_FILTER, IDC_FILTER,
IDC_PCRTC_OFFSETS, IDC_PCRTC_OFFSETS,
IDC_PCRTC_OVERSCAN, IDC_PCRTC_OVERSCAN,
IDC_PCRTC_ANTIBLUR,
IDC_DISABLE_INTERLACE_OFFSETS, IDC_DISABLE_INTERLACE_OFFSETS,
// Hardware Renderer // Hardware Renderer
IDC_PRELOAD_TEXTURES, IDC_PRELOAD_TEXTURES,

View File

@ -324,7 +324,7 @@ RendererTab::RendererTab(wxWindow* parent)
m_ui.addCheckBox(pcrtc_checks_box, "Screen Offsets", "pcrtc_offsets", IDC_PCRTC_OFFSETS); m_ui.addCheckBox(pcrtc_checks_box, "Screen Offsets", "pcrtc_offsets", IDC_PCRTC_OFFSETS);
m_ui.addCheckBox(pcrtc_checks_box, "Show Overscan", "pcrtc_overscan", IDC_PCRTC_OVERSCAN); m_ui.addCheckBox(pcrtc_checks_box, "Show Overscan", "pcrtc_overscan", IDC_PCRTC_OVERSCAN);
m_ui.addCheckBox(pcrtc_checks_box, "Disable Interlace Offset", "disable_interlace_offset", IDC_DISABLE_INTERLACE_OFFSETS); m_ui.addCheckBox(pcrtc_checks_box, "Disable Interlace Offset", "disable_interlace_offset", IDC_DISABLE_INTERLACE_OFFSETS);
m_ui.addCheckBox(pcrtc_checks_box, "Anti-Blur", "pcrtc_antiblur", IDC_PCRTC_ANTIBLUR);
general_box->Add(pcrtc_checks_box, wxSizerFlags().Center()); general_box->Add(pcrtc_checks_box, wxSizerFlags().Center());
tab_box->Add(hardware_box.outer, wxSizerFlags().Expand()); tab_box->Add(hardware_box.outer, wxSizerFlags().Expand());

View File

@ -300,6 +300,7 @@ Pcsx2Config::GSOptions::GSOptions()
{ {
bitset = 0; bitset = 0;
PCRTCAntiBlur = true;
DisableInterlaceOffset = false; DisableInterlaceOffset = false;
PCRTCOffsets = false; PCRTCOffsets = false;
PCRTCOverscan = false; PCRTCOverscan = false;
@ -510,6 +511,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
// Unfortunately, because code in the GS still reads the setting by key instead of // Unfortunately, because code in the GS still reads the setting by key instead of
// using these variables, we need to use the old names. Maybe post 2.0 we can change this. // using these variables, we need to use the old names. Maybe post 2.0 we can change this.
GSSettingBoolEx(PCRTCAntiBlur, "pcrtc_antiblur");
GSSettingBoolEx(DisableInterlaceOffset, "disable_interlace_offset"); GSSettingBoolEx(DisableInterlaceOffset, "disable_interlace_offset");
GSSettingBoolEx(PCRTCOffsets, "pcrtc_offsets"); GSSettingBoolEx(PCRTCOffsets, "pcrtc_offsets");
GSSettingBoolEx(PCRTCOverscan, "pcrtc_overscan"); GSSettingBoolEx(PCRTCOverscan, "pcrtc_overscan");