diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
index c5094f3f07d3a..63af7cc4c487f 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 6d70ab70c36b9..4f66030b652ae 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,33 @@
- -
-
+
-
+
+
+ Disables interlacing offset which may reduce blurring in some situations.
+
- Internal Resolution Screenshots
+ Disable Interlace Offset
- -
-
+
-
+
- Integer Upscaling
+ Internal Resolution Screenshots
- -
-
+
-
+
+
+ Enables internal Anti-Blur hacks. Less accurate to PS2 rendering but will make a lot of games look less blurry.
+
- Show Overscan
+ Anti-Blur
+
+
+ Ctrl+S
@@ -313,26 +329,20 @@
- -
-
-
- Disables interlacing offset which may reduce blurring in some situations.
-
+
-
+
- Disable Interlace Offset
+ Show Overscan
- -
-
+
-
+
- Enables internal Anti-Blur hacks. Less accurate to PS2 rendering but will make a lot of games look less blurry.
+ Attempts to pre-round sprite texel coordinates to resolve rounding issues. Helpful for games such as Beyond Good and Evil, and Manhunt
- Anti-Blur
-
-
- Ctrl+S
+ Pre-Round Sprites
diff --git a/pcsx2/Config.h b/pcsx2/Config.h
index 060a10de26c36..0301420a5e97f 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 1d009a0cd1b4c..ae92593f756b6 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 7411f396902b5..1006d26f519de 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 3b79171248971..fad0aa5b07f4a 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 806f1e97fb2fb..e83a42f51481c 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 0bd299961129f..e22bd052b7d9f 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 048a60c4eeeab..21f413e7904f9 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 87a6ae2a99b31..c508162f20c65 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 d3aa6903027ae..f36fae899ea55 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
@@ -504,6 +506,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 d24ecafefb41d..a47165aa25295 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 c3641b147908a..e0a93134153c8 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");