From b5807e0788e3bafa9ac7e6dd1cf8a86b97662a63 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 14 Jun 2022 23:54:08 +0100 Subject: [PATCH] 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 --- pcsx2-qt/Settings/GraphicsSettingsWidget.cpp | 1 + pcsx2-qt/Settings/GraphicsSettingsWidget.ui | 13 +++ pcsx2/Config.h | 1 + pcsx2/GS/GS.cpp | 1 + pcsx2/GS/Renderers/Common/GSDevice.cpp | 5 +- pcsx2/GS/Renderers/Common/GSRenderer.cpp | 104 +++++++++++-------- pcsx2/GS/Window/GSSetting.cpp | 3 + pcsx2/GS/Window/GSSetting.h | 1 + pcsx2/GS/Window/GSwxDialog.cpp | 2 +- pcsx2/Pcsx2Config.cpp | 2 + 10 files changed, 85 insertions(+), 48 deletions(-) diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index e34bb95788..502d8127c0 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -105,6 +105,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget* 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.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.internalResolutionScreenshots, "EmuCore/GS", "InternalResolutionScreenshots", false); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.zoom, "EmuCore/GS", "Zoom", 100.0f); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index bd9c0947f3..5a0085f1de 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -323,6 +323,19 @@ + + + + Enables internal Anti-Blur hacks. LEss accurate to PS2 rendering but will make a lot of games look less blurry. + + + Anti-Blur + + + Ctrl+S + + + diff --git a/pcsx2/Config.h b/pcsx2/Config.h index bd8169909e..ba7c991321 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -434,6 +434,7 @@ struct Pcsx2Config struct { bool + PCRTCAntiBlur : 1, DisableInterlaceOffset : 1, PCRTCOffsets : 1, PCRTCOverscan : 1, diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 365c7ecaaf..2436607ced 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -1370,6 +1370,7 @@ void GSApp::Init() m_default_configuration["fxaa"] = "0"; m_default_configuration["GSDumpCompression"] = "0"; m_default_configuration["HWDisableReadbacks"] = "0"; + m_default_configuration["pcrtc_antiblur"] = "1"; m_default_configuration["disable_interlace_offset"] = "0"; m_default_configuration["pcrtc_offsets"] = "0"; m_default_configuration["pcrtc_overscan"] = "0"; diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp index ef1296a643..29d463da14 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.cpp +++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp @@ -377,7 +377,7 @@ void GSDevice::Interlace(const GSVector2i& ds, int field, int mode, float yoffse if (mode == 0 || mode == 2) // weave or blend { // weave first - const int offset = static_cast(yoffset) * (1 - field); + const int offset = static_cast(yoffset) * field; 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 { - 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; } diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index 927057c818..8a68214de5 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -95,6 +95,7 @@ bool GSRenderer::Merge(int field) GSVector2i frame_baseline = {INT_MAX, INT_MAX}; GSVector2i display_combined = {0, 0}; bool feedback_merge = m_regs->EXTWRITE.WRITE == 1; + bool display_offset = false; 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.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("[%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);*/ @@ -168,7 +170,7 @@ bool GSRenderer::Merge(int field) const bool slbg = m_regs->PMODE.SLBG; GSVector2i resolution(GetResolution()); - bool scanmask_frame = true; + bool scanmask_frame = m_scanmask_used && !display_offset; const bool ignore_offset = !GSConfig.PCRTCOffsets; for (int i = 0; i < 2; i++) @@ -186,55 +188,64 @@ bool GSRenderer::Merge(int field) // If using scanmsk we have to keep the single line offset, regardless of upscale // so we handle this separately after the rect calculations. - const int interlace_offset = display_diff.y & 1; - - if (m_scanmask_used && interlace_offset) + float interlace_offset = 0.0f; + + if ((!GSConfig.PCRTCAntiBlur || m_scanmask_used) && display_offset) { - display_diff.y &= ~1; - scanmask_frame = false; + interlace_offset = static_cast(display_diff.y & 1); + + // 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) off.y &= ~1; + + display_diff.y &= ~1; } // Start of Anti-Blur code. if (!ignore_offset) { - if (samesrc) + if (GSConfig.PCRTCAntiBlur) { - // Offset by DISPLAY setting - if (display_diff.x < 4) - off.x -= display_diff.x; - if (display_diff.y < 4) - off.y -= display_diff.y; + if (samesrc) + { + // Offset by DISPLAY setting + if (display_diff.x < 4) + off.x -= display_diff.x; + if (display_diff.y < 4) + off.y -= display_diff.y; - // Offset by DISPFB setting - if (frame_diff.x == 1) - off.x += 1; - if (frame_diff.y == 1) - off.y += 1; + // Offset by DISPFB setting + if (frame_diff.x == 1) + off.x += 1; + if (frame_diff.y == 1) + off.y += 1; + } } } else { if (!slbg || !feedback_merge) { - const int videomode = static_cast(GetVideoMode()) - 1; - const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; - GSVector2i base_resolution(offsets.x, offsets.y); - - if (isinterlaced() && !m_regs->SMODE2.FFMD) - 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) + if (display_diff.x >= 4 || !GSConfig.PCRTCAntiBlur) off.x = display_diff.x; - if (display_diff.y >= 4) + if (display_diff.y >= 4 || !GSConfig.PCRTCAntiBlur) off.y = display_diff.y; - // Anti blur hax. + if (samesrc) { + // Adjusting the screen offset when using a negative offset. + const int videomode = static_cast(GetVideoMode()) - 1; + const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; + GSVector2i base_resolution(offsets.x, offsets.y); + + if (isinterlaced() && !m_regs->SMODE2.FFMD) + base_resolution.y *= 2; + // Offset by DISPLAY setting if (display_diff.x < 0) { @@ -249,17 +260,21 @@ bool GSRenderer::Merge(int field) resolution.y -= display_diff.y; } - // Offset by DISPFB setting - if (frame_diff.x == 1) - off.x += 1; - - if (frame_diff.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; + + // Anti-Blur stuff + if (GSConfig.PCRTCAntiBlur) + { + // Offset by DISPFB setting + if (frame_diff.x == 1) + off.x += 1; + + if (frame_diff.y == 1) + off.y += 1; + } } } } @@ -277,9 +292,8 @@ bool GSRenderer::Merge(int field) // src_out_rect is the resized rect for output. (Not really used) src_out_rect[i] = (GSVector4(r) * scale) / GSVector4(tex[i]->GetSize()).xyxy(); - // Restore the single line offset for scanmsk. - if (m_scanmask_used && interlace_offset) - dst[i] += GSVector4(0.0f, 1.0f, 0.0f, 1.0f); + // Restore manually offset "interlace" lines + dst[i] += GSVector4(0.0f, interlace_offset, 0.0f, interlace_offset); } 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); - // Offset is not compatible with scanmsk, as scanmsk renders every other line, but at x7 the interlace offset will be 7 lines - const int offset = (m_scanmask_used || !m_regs->SMODE2.FFMD) ? 0 : (int)(tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y); + // Use offset for bob deinterlacing, for normal deinterlacing we add an extra 1 since it's clearer, with bob that causes extra shake. + 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) { - const bool scanmask = m_scanmask_used && scanmask_frame && GSConfig.InterlaceMode == GSInterlaceMode::Automatic; - - if (GSConfig.InterlaceMode == GSInterlaceMode::Automatic && (m_regs->SMODE2.FFMD)) // Auto interlace enabled / Odd frame interlace setting + // 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)) { - constexpr int field2 = 1; + constexpr int field2 = 0; constexpr int mode = 2; g_gs_device->Interlace(ds, field ^ field2, mode, offset); } else { - const int field2 = scanmask ? 0 : 1 - ((static_cast(GSConfig.InterlaceMode) - 1) & 1); - const int mode = scanmask ? 2 : ((static_cast(GSConfig.InterlaceMode) - 1) >> 1); + const int field2 = ((static_cast(GSConfig.InterlaceMode) - 1) & 1); + const int mode = ((static_cast(GSConfig.InterlaceMode) - 1) >> 1); g_gs_device->Interlace(ds, field ^ field2, mode, offset); } diff --git a/pcsx2/GS/Window/GSSetting.cpp b/pcsx2/GS/Window/GSSetting.cpp index 9200e1a328..0bd2999611 100644 --- a/pcsx2/GS/Window/GSSetting.cpp +++ b/pcsx2/GS/Window/GSSetting.cpp @@ -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" "Used for games like Need for Speed Underground (PAL),Crash Nitro Kart (PAL) \n" "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: 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" diff --git a/pcsx2/GS/Window/GSSetting.h b/pcsx2/GS/Window/GSSetting.h index 5c9986cf0a..048a60c4ee 100644 --- a/pcsx2/GS/Window/GSSetting.h +++ b/pcsx2/GS/Window/GSSetting.h @@ -44,6 +44,7 @@ enum IDC_FILTER, IDC_PCRTC_OFFSETS, IDC_PCRTC_OVERSCAN, + IDC_PCRTC_ANTIBLUR, IDC_DISABLE_INTERLACE_OFFSETS, // Hardware Renderer IDC_PRELOAD_TEXTURES, diff --git a/pcsx2/GS/Window/GSwxDialog.cpp b/pcsx2/GS/Window/GSwxDialog.cpp index 1869a4ec29..0909a2b52d 100644 --- a/pcsx2/GS/Window/GSwxDialog.cpp +++ b/pcsx2/GS/Window/GSwxDialog.cpp @@ -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, "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, "Anti-Blur", "pcrtc_antiblur", IDC_PCRTC_ANTIBLUR); general_box->Add(pcrtc_checks_box, wxSizerFlags().Center()); tab_box->Add(hardware_box.outer, wxSizerFlags().Expand()); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 847d57db37..8777a527bc 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -300,6 +300,7 @@ Pcsx2Config::GSOptions::GSOptions() { bitset = 0; + PCRTCAntiBlur = true; DisableInterlaceOffset = false; PCRTCOffsets = 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 // 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(PCRTCOffsets, "pcrtc_offsets"); GSSettingBoolEx(PCRTCOverscan, "pcrtc_overscan");