From 263a401440c5f79c5df7429e89d6ae2a4d94519c Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 30 Jun 2022 21:15:52 +0100 Subject: [PATCH] GS: Add Pre-Round Sprite hack This attempts to preproduce hardware behaviour, but falls down in a bunch of cases, hence the hack. --- pcsx2-qt/Settings/GraphicsSettingsWidget.cpp | 1 + pcsx2-qt/Settings/GraphicsSettingsWidget.ui | 54 ++++--- pcsx2/Config.h | 1 + pcsx2/GS/GS.cpp | 1 + pcsx2/GS/GSState.cpp | 140 ++++++++++++++++++- pcsx2/GS/GSState.h | 1 + pcsx2/GS/Renderers/Common/GSRenderer.cpp | 2 +- pcsx2/GS/Window/GSSetting.cpp | 4 + pcsx2/GS/Window/GSSetting.h | 1 + pcsx2/GS/Window/GSwxDialog.cpp | 1 + pcsx2/GameDatabase.cpp | 5 + pcsx2/GameDatabase.h | 1 + pcsx2/Pcsx2Config.cpp | 2 + 13 files changed, 187 insertions(+), 27 deletions(-) diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 4a96a68c04a98f..05520be83dedec 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -113,6 +113,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget* 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.PreRoundSprites, "EmuCore/GS", "preround_sprites", 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.stretchY, "EmuCore/GS", "StretchY", 100.0f); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index a5a3c86465bc60..66b23558a62e4b 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -6,7 +6,7 @@ 0 0 - 657 + 652 890 @@ -278,6 +278,13 @@ + + + + Integer Upscaling + + + @@ -285,24 +292,30 @@ - - + + + + Disables interlacing offset which may reduce blurring in some situations. + - Internal Resolution Screenshots + Disable Interlace Offset - - + + - Integer Upscaling + Internal Resolution Screenshots - - + + - Show Overscan + Anti-Blur + + + Ctrl+S @@ -313,23 +326,20 @@ - - - - Disables interlacing offset which may reduce blurring in some situations. - + + - Disable Interlace Offset + Show Overscan - - - - Anti-Blur + + + + Attempts to pre-round sprite texel coordinates to resolve rounding issues. Helpful for games such as Beyond Good and Evil, and Manhunt - - Ctrl+S + + Pre-Round Sprites diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 6abf912f52786c..306a9018cd6165 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -468,6 +468,7 @@ struct Pcsx2Config Mipmap : 1, AA1 : 1, PointListPalette : 1, + PreRoundSprites : 1, ManualUserHacks : 1, UserHacks_AlignSpriteX : 1, UserHacks_AutoFlush : 1, diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 7616851e42149e..fb1384bfa3097f 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -1488,6 +1488,7 @@ void GSApp::Init() m_default_configuration["paltex"] = "0"; m_default_configuration["png_compression_level"] = std::to_string(Z_BEST_SPEED); m_default_configuration["PointListPalette"] = "0"; + m_default_configuration["preround_sprites"] = "0"; m_default_configuration["PrecacheTextureReplacements"] = "0"; m_default_configuration["preload_frame_with_gs_data"] = "0"; m_default_configuration["Renderer"] = std::to_string(static_cast(GSRendererType::Auto)); diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index 7411f396902b5e..71faba1a51a4eb 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -675,7 +675,7 @@ void GSState::DumpVertices(const std::string& filename) file << uv_U << DEL << uv_V; } else - file << v.ST.S << DEL << v.ST.T << DEL << v.RGBAQ.Q; + file << v.ST.S << "(" << *(u32*)&v.ST.S << ")" << DEL << v.ST.T << "(" << *(u32*)&v.ST.T << ")" << DEL << v.RGBAQ.Q << "(" << *(u32*)&v.RGBAQ.Q << ")"; file << std::endl; } @@ -1769,7 +1769,17 @@ inline bool GSState::TestDrawChanged() return false; } - +u32 GSState::CalcMask(int exp, int max_exp) +{ + int amount = 9 + (max_exp - exp); + u32 result = 0x1ff; + + for (int i = 9; i < amount; i++) + { + result |= 1 << i; + } + return result; +} void GSState::FlushPrim() { if (m_index.tail > 0) @@ -1830,6 +1840,128 @@ void GSState::FlushPrim() ASSERT((int)unused < GSUtil::GetVertexCount(PRIM->PRIM)); } + // Texel coordinate rounding + // Helps Beyond Good & Evil (water) and Manhunt (lights shining through objects). + // Dark Cloud 2 also benefits in some ways but is broken in others. + // Can help with some alignment issues when upscaling too, and is for both Software and Hardware renderers. + // This is *NOT* 100% safe, some games hate it (Gran Turismo 4's post processing for example). + // I'm sure some of this is wrong, so as of now it serves as a hack fix. + if (m_env.PRIM.TME &&/* !m_context->TEX1.MMAG && !(m_context->TEX1.MMIN & 1) &&*/ GSConfig.PreRoundSprites) + { + if (m_env.PRIM.FST) // UV's + { + // UV's on sprites only alter the final UV, I believe the problem is we're drawing too much (drawing stops earlier than we do) + // But this fixes Beyond Good adn Evil water and Dark Cloud 2's UI + if (GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS) + { + for (int i = 0; i < m_vertex.tail; i+=2) + { + GSVertex* v1 = &m_vertex.buff[i]; + GSVertex* v2 = &m_vertex.buff[i + 1]; + + GSVertex* vu1; + GSVertex* vu2; + GSVertex* vv1; + GSVertex* vv2; + if (v1->U > v2->U) + { + vu2 = v1; + vu1 = v2; + } + else + { + vu2 = v2; + vu1 = v1; + } + + if (v1->V > v2->V) + { + vv2 = v1; + vv1 = v2; + } + else + { + vv2 = v2; + vv1 = v1; + } + + const GSVector2 pos_range(vu2->XYZ.X - vu1->XYZ.X, vv2->XYZ.Y - vv1->XYZ.Y); + const GSVector2 uv_range(vu2->U - vu1->U, vv2->V - vv1->V); + const GSVector2 grad(uv_range / pos_range); + bool isclamp_w = m_context->CLAMP.WMS > 0 && m_context->CLAMP.WMS < 3; + bool isclamp_h = m_context->CLAMP.WMT > 0 && m_context->CLAMP.WMT < 3; + int max_w = (m_context->CLAMP.WMS == 2) ? m_context->CLAMP.MAXU : ((1 << m_context->TEX0.TW) - 1); + int max_h = (m_context->CLAMP.WMT == 2) ? m_context->CLAMP.MAXV : ((1 << m_context->TEX0.TH) - 1); + int width = vu2->U >> 4; + int height = vv2->V >> 4; + + if (m_context->CLAMP.WMS == 3) + width = (width & m_context->CLAMP.MINU) | m_context->CLAMP.MAXU; + + if (m_context->CLAMP.WMT == 3) + height = (height & m_context->CLAMP.MINV) | m_context->CLAMP.MAXV; + + if ((isclamp_w && width <= max_w && grad.x != 1.0f) || !isclamp_w) + { + if (vu2->U >= 0x1) + { + vu2->U = (vu2->U - 1); + /*if (!isclamp_w && vu2->XYZ.X < m_context->scissor.ofex.z) + vu2->XYZ.X -= 1;*/ + } + } + else + vu2->U &= ~0xf; + + // Dark Cloud 2 tries to address wider than the TBW when in CLAMP mode (maybe some weird behaviour in HW?) + if ((vu2->U >> 4) > (int)(m_context->TEX0.TBW * 64) && isclamp_w) + { + vu2->U = (m_context->TEX0.TBW * 64) << 4; + } + + if ((isclamp_h && height <= max_h && grad.y != 1.0f) || !isclamp_h) + { + if (vv2->V >= 0x1) + { + vv2->V = (vv2->V - 1); + /*if (!isclamp_h && vv2->XYZ.Y < m_context->scissor.ofex.w) + vv2->XYZ.Y -= 1;*/ + } + } + else + vv2->V &= ~0xf; + } + } + } + else + { + // ST's have the lowest 8 bits rounding down (Manhunt lighting) + for (int i = 0; i < m_vertex.tail; i++) + { + // Don't worry, I hate me too. If there's a modern way to do bit manipulation on floats, I'd love to hear it! + GSVertex* v = &m_vertex.buff[i]; + u32 S = *(u32*)&v->ST.S; + u32 T = *(u32*)&v->ST.T; + u32 Q = *(u32*)&v->RGBAQ.Q; + int expS = (S >> 23) & 0xff; + int expT = (T >> 23) & 0xff; + int expQ = (Q >> 23) & 0xff; + int max_exp = std::max(expS, expT); + max_exp = std::max(max_exp, expQ); + + u32 mask = CalcMask(expS, max_exp); + S &= ~mask; + v->ST.S = *(float*)&S; + mask = CalcMask(expT, max_exp); + T &= ~mask; + v->ST.T = *(float*)&T; + mask = CalcMask(expQ, max_exp); + Q &= ~mask; + v->RGBAQ.Q = *(float*)&Q; + } + } + } + // If the PSM format of Z is invalid, but it is masked (no write) and ZTST is set to ALWAYS pass (no test, just allow) // we can ignore the Z format, since it won't be used in the draw (Star Ocean 3 transitions) const bool ignoreZ = m_context->ZBUF.ZMSK && m_context->TEST.ZTST == 1; @@ -1869,7 +2001,6 @@ void GSState::FlushPrim() if (unused > 0) { memcpy(m_vertex.buff, buff, sizeof(GSVertex) * unused); - m_vertex.tail = unused; m_vertex.next = next > head ? next - head : 0; } @@ -3030,10 +3161,11 @@ __forceinline void GSState::VertexKick(u32 skip) GSVector4i v1(m_v.m[1]); GSVector4i* RESTRICT tailptr = (GSVector4i*)&m_vertex.buff[tail]; - + tailptr[0] = v0; tailptr[1] = v1; + const GSVector4i xy = v1.xxxx().u16to32().sub32(m_ofxy); GSVector4i::storel(&m_vertex.xy[xy_tail & 3], xy.blend16<0xf0>(xy.sra32(4)).ps32()); diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 3b791712489713..fad0aa5b07f4ad 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -341,6 +341,7 @@ class GSState : public GSAlignedClass<32> void CopyEnv(GSDrawingEnvironment* dest, GSDrawingEnvironment* src, int ctx); void Flush(); + u32 CalcMask(int exp, int max_exp); void FlushPrim(); bool TestDrawChanged(); void FlushWrite(); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index 806f1e97fb2fb7..e83a42f51481c2 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -310,7 +310,7 @@ bool GSRenderer::Merge(int field) { // We do half because FFMD is a half sized framebuffer, then we offset by 1 in the shader for the actual interlace if(GetUpscaleMultiplier() > 1) - interlace_offset += ((((tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y) + 0.5f) * 0.5f) - 1.0f) * static_cast(field ^ field2); + interlace_offset += ((((tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y)) * 0.5f)) * static_cast(field ^ field2); offset = 1.0f; } // Restore manually offset "interlace" lines diff --git a/pcsx2/GS/Window/GSSetting.cpp b/pcsx2/GS/Window/GSSetting.cpp index 0bd299961129f7..e22bd052b7d9f1 100644 --- a/pcsx2/GS/Window/GSSetting.cpp +++ b/pcsx2/GS/Window/GSSetting.cpp @@ -104,6 +104,10 @@ const char* dialog_message(int ID, bool* updateText) 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" "Used for ICO to reduce blur."); + case IDC_PREROUND_SPRITES: + return cvtString("Enable: Attempts to pre-round sprite texel coordinates.\n" + "Can improve effects in some games, such as the water in Beyond Good and Evil\n" + "and the light bleed in Manhunt"); case IDC_ACCURATE_DATE: return cvtString("Implement a more accurate algorithm to compute GS destination alpha testing.\n" "It improves shadow and transparency rendering.\n\n" diff --git a/pcsx2/GS/Window/GSSetting.h b/pcsx2/GS/Window/GSSetting.h index 048a60c4eeeab6..21f413e7904f92 100644 --- a/pcsx2/GS/Window/GSSetting.h +++ b/pcsx2/GS/Window/GSSetting.h @@ -46,6 +46,7 @@ enum IDC_PCRTC_OVERSCAN, IDC_PCRTC_ANTIBLUR, IDC_DISABLE_INTERLACE_OFFSETS, + IDC_PREROUND_SPRITES, // Hardware Renderer IDC_PRELOAD_TEXTURES, IDC_ACCURATE_DATE, diff --git a/pcsx2/GS/Window/GSwxDialog.cpp b/pcsx2/GS/Window/GSwxDialog.cpp index 87a6ae2a99b31d..c508162f20c657 100644 --- a/pcsx2/GS/Window/GSwxDialog.cpp +++ b/pcsx2/GS/Window/GSwxDialog.cpp @@ -325,6 +325,7 @@ RendererTab::RendererTab(wxWindow* parent) 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); + m_ui.addCheckBox(pcrtc_checks_box, "Pre-Round Sprites", "preround_sprites", IDC_PREROUND_SPRITES); general_box->Add(pcrtc_checks_box, wxSizerFlags().Center()); tab_box->Add(hardware_box.outer, wxSizerFlags().Expand()); diff --git a/pcsx2/GameDatabase.cpp b/pcsx2/GameDatabase.cpp index d812363b18aaeb..2984d2e667683b 100644 --- a/pcsx2/GameDatabase.cpp +++ b/pcsx2/GameDatabase.cpp @@ -281,6 +281,7 @@ static const char* s_gs_hw_fix_names[] = { "mergeSprite", "wildArmsHack", "pointListPalette", + "preRoundSprites", "mipmap", "trilinearFiltering", "skipDrawStart", @@ -319,6 +320,7 @@ bool GameDatabaseSchema::isUserHackHWFix(GSHWFixId id) case GSHWFixId::TexturePreloading: case GSHWFixId::ConservativeFramebuffer: case GSHWFixId::PointListPalette: + case GSHWFixId::PreRoundSprites: return false; #ifdef PCSX2_CORE @@ -585,6 +587,9 @@ u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions& case GSHWFixId::PointListPalette: config.PointListPalette = (value > 0); break; + case GSHWFixId::PreRoundSprites: + config.PreRoundSprites = (value > 0); + break; case GSHWFixId::Mipmap: { diff --git a/pcsx2/GameDatabase.h b/pcsx2/GameDatabase.h index 04f4fed8687b72..3797fb37da4f10 100644 --- a/pcsx2/GameDatabase.h +++ b/pcsx2/GameDatabase.h @@ -71,6 +71,7 @@ namespace GameDatabaseSchema MergeSprite, WildArmsHack, PointListPalette, + PreRoundSprites, // integer settings Mipmap, diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index c3641b147908aa..e0a93134153c8c 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -332,6 +332,7 @@ Pcsx2Config::GSOptions::GSOptions() Mipmap = true; AA1 = true; PointListPalette = false; + PreRoundSprites = false; ManualUserHacks = false; UserHacks_AlignSpriteX = false; @@ -533,6 +534,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings() GSSettingBool(OsdShowResolution); GSSettingBool(OsdShowGSStats); GSSettingBool(OsdShowIndicators); + GSSettingBoolEx(PreRoundSprites, "preround_sprites"); GSSettingBool(HWDisableReadbacks); GSSettingBoolEx(AccurateDATE, "accurate_date");