diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index fca9cb3df9156..83a5bef617e56 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -115,6 +115,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToIntSetting( sif, m_ui.screenshotFormat, "EmuCore/GS", "ScreenshotFormat", static_cast(GSScreenshotFormat::PNG)); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.screenshotQuality, "EmuCore/GS", "ScreenshotQuality", 50); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PreRoundSprites, "EmuCore/GS", "preround_sprites", false); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.stretchY, "EmuCore/GS", "StretchY", 100.0f); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cropLeft, "EmuCore/GS", "CropLeft", 0); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cropTop, "EmuCore/GS", "CropTop", 0); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index 0424d9ff6cc65..dd422a8be5f91 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -384,6 +384,16 @@ + + + + Attempts to pre-round sprite texel coordinates to resolve rounding issues. Helpful for games such as Beyond Good and Evil, and Manhunt + + + Pre-Round Sprites + + + diff --git a/pcsx2/Config.h b/pcsx2/Config.h index c7a95a2ce49d0..05984e41bcc47 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -685,6 +685,7 @@ struct Pcsx2Config AutoFlushSW : 1, PreloadFrameWithGSData : 1, Mipmap : 1, + PreRoundSprites : 1, ManualUserHacks : 1, UserHacks_AlignSpriteX : 1, UserHacks_CPUFBConversion : 1, diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index 87b2917c95332..9de33ac54d4f4 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -1680,9 +1680,98 @@ void GSState::FlushPrim() // Helps Manhunt (lights shining through objects). // Can help with some alignment issues when upscaling too, and is for both Software and Hardware renderers. // Sometimes hardware doesn't get affected, likely due to the difference in how GPU's handle textures (Persona minimap). - if (m_env.PRIM.TME && (GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS || m_vt.m_eq.z)) + if (PRIM->TME && (GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS || m_vt.m_eq.z)) { - if (!m_env.PRIM.FST) // STQ's + if (PRIM->FST && GSConfig.PreRoundSprites) // 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 (u32 i = 0; i < m_index.tail; i += 2) + { + GSVertex* v1 = &m_vertex.buff[m_index.buff[i]]; + GSVertex* v2 = &m_vertex.buff[m_index.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; + } + + 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); + if (pos_range.x == 0) + pos_range.x = 1; + if (pos_range.y == 0) + pos_range.y = 1; + 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 & 0xf) || grad.x <= 1.0f)) + { + vu2->U = (vu2->U - 1); + /*if (!isclamp_w && ((vu2->XYZ.X - m_context->XYOFFSET.OFX) >> 4) < m_context->scissor.in.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 >> 4) - 8 <= (int)(m_context->TEX0.TBW * 64)) + { + 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 & 0xf) || grad.y <= 1.0f)) + { + vv2->V = (vv2->V - 1); + /*if (!isclamp_h && ((vv2->XYZ.Y - m_context->XYOFFSET.OFY) >> 4) < m_context->scissor.in.w) + vv2->XYZ.Y -= 1;*/ + } + } + else + vv2->V &= ~0xf; + } + } + } + else if (!PRIM->FST) // STQ's { const bool is_sprite = GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS; // ST's have the lowest 9 bits (or greater depending on exponent difference) rounding down (from hardware tests). @@ -1735,7 +1824,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; @@ -3364,7 +3452,6 @@ __forceinline void GSState::VertexKick(u32 skip) const GSVector4i new_v1(m_v.m[1]); GSVector4i* RESTRICT tailptr = (GSVector4i*)&m_vertex.buff[tail]; - tailptr[0] = new_v0; tailptr[1] = new_v1; diff --git a/pcsx2/GameDatabase.cpp b/pcsx2/GameDatabase.cpp index 4603c28c845e7..6e2af87b1dbbb 100644 --- a/pcsx2/GameDatabase.cpp +++ b/pcsx2/GameDatabase.cpp @@ -356,6 +356,7 @@ static const char* s_gs_hw_fix_names[] = { "estimateTextureRegion", "PCRTCOffsets", "PCRTCOverscan", + "preRoundSprites", "mipmap", "trilinearFiltering", "skipDrawStart", @@ -402,6 +403,7 @@ bool GameDatabaseSchema::isUserHackHWFix(GSHWFixId id) case GSHWFixId::Deinterlace: case GSHWFixId::Mipmap: case GSHWFixId::TexturePreloading: + case GSHWFixId::PreRoundSprites: case GSHWFixId::TrilinearFiltering: case GSHWFixId::MinimumBlendingLevel: case GSHWFixId::MaximumBlendingLevel: @@ -747,6 +749,10 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions& config.PCRTCOverscan = (value > 0); break; + case GSHWFixId::PreRoundSprites: + config.PreRoundSprites = (value > 0); + break; + case GSHWFixId::Mipmap: { if (value >= 0 && value <= static_cast(HWMipmapLevel::Full)) diff --git a/pcsx2/GameDatabase.h b/pcsx2/GameDatabase.h index f111c27853e43..4461c82f71822 100644 --- a/pcsx2/GameDatabase.h +++ b/pcsx2/GameDatabase.h @@ -75,6 +75,7 @@ namespace GameDatabaseSchema EstimateTextureRegion, PCRTCOffsets, PCRTCOverscan, + PreRoundSprites, // integer settings Mipmap, diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index accef4b1df9c6..0a4b57b821b7b 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -526,6 +526,7 @@ Pcsx2Config::GSOptions::GSOptions() AutoFlushSW = true; PreloadFrameWithGSData = false; Mipmap = true; + PreRoundSprites = false; ManualUserHacks = false; UserHacks_AlignSpriteX = false; @@ -734,6 +735,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) GSSettingBool(OsdShowSettings); GSSettingBool(OsdShowInputs); GSSettingBool(OsdShowFrameTimes); + GSSettingBoolEx(PreRoundSprites, "preround_sprites"); GSSettingBool(HWSpinGPUForReadbacks); GSSettingBool(HWSpinCPUForReadbacks);