Skip to content

Commit c66b047

Browse files
GS: Add Pre-Round Sprite hack
This attempts to preproduce hardware behaviour, but falls down in a bunch of cases, hence the hack.
1 parent 6d7deb5 commit c66b047

File tree

7 files changed

+112
-3
lines changed

7 files changed

+112
-3
lines changed

pcsx2-qt/Settings/GraphicsDisplaySettingsTab.ui

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@
8484
</property>
8585
</widget>
8686
</item>
87+
<item row="3" column="1">
88+
<widget class="QCheckBox" name="PreRoundSprites">
89+
<property name="toolTip">
90+
<string>Attempts to pre-round sprite texel coordinates to resolve rounding issues. Helpful for games such as Beyond Good and Evil, and Manhunt</string>
91+
</property>
92+
<property name="text">
93+
<string>Pre-Round Sprites</string>
94+
</property>
95+
</widget>
96+
</item>
8797
</layout>
8898
</item>
8999
<item row="2" column="0">

pcsx2-qt/Settings/GraphicsSettingsWidget.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,14 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
9595
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_display.integerScaling, "EmuCore/GS", "IntegerScaling", false);
9696
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_display.PCRTCOffsets, "EmuCore/GS", "pcrtc_offsets", false);
9797
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_display.PCRTCOverscan, "EmuCore/GS", "pcrtc_overscan", false);
98+
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_display.PreRoundSprites, "EmuCore/GS", "preround_sprites", false);
9899
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_display.PCRTCAntiBlur, "EmuCore/GS", "pcrtc_antiblur", true);
99100
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_display.DisableInterlaceOffset, "EmuCore/GS", "disable_interlace_offset", false);
100101
SettingWidgetBinder::BindWidgetToIntSetting(
101102
sif, m_capture.screenshotSize, "EmuCore/GS", "ScreenshotSize", static_cast<int>(GSScreenshotSize::WindowResolution));
102103
SettingWidgetBinder::BindWidgetToIntSetting(
103104
sif, m_capture.screenshotFormat, "EmuCore/GS", "ScreenshotFormat", static_cast<int>(GSScreenshotFormat::PNG));
105+
104106
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_display.stretchY, "EmuCore/GS", "StretchY", 100.0f);
105107
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_display.cropLeft, "EmuCore/GS", "CropLeft", 0);
106108
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_display.cropTop, "EmuCore/GS", "CropTop", 0);

pcsx2/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ struct Pcsx2Config
753753
PreloadFrameWithGSData : 1,
754754
Mipmap : 1,
755755
HWMipmap : 1,
756+
PreRoundSprites : 1,
756757
ManualUserHacks : 1,
757758
UserHacks_AlignSpriteX : 1,
758759
UserHacks_CPUFBConversion : 1,

pcsx2/GS/GSState.cpp

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,7 +1729,96 @@ void GSState::FlushPrim()
17291729
// Sometimes hardware doesn't get affected, likely due to the difference in how GPU's handle textures (Persona minimap).
17301730
if (PRIM->TME && (GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS || m_vt.m_eq.z))
17311731
{
1732-
if (!PRIM->FST) // STQ's
1732+
if (PRIM->FST && GSConfig.PreRoundSprites) // UV's
1733+
{
1734+
// 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)
1735+
// But this fixes Beyond Good adn Evil water and Dark Cloud 2's UI
1736+
if (GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS)
1737+
{
1738+
for (u32 i = 0; i < m_index.tail; i += 2)
1739+
{
1740+
GSVertex* v1 = &m_vertex.buff[m_index.buff[i]];
1741+
GSVertex* v2 = &m_vertex.buff[m_index.buff[i + 1]];
1742+
1743+
GSVertex* vu1;
1744+
GSVertex* vu2;
1745+
GSVertex* vv1;
1746+
GSVertex* vv2;
1747+
if (v1->U > v2->U)
1748+
{
1749+
vu2 = v1;
1750+
vu1 = v2;
1751+
}
1752+
else
1753+
{
1754+
vu2 = v2;
1755+
vu1 = v1;
1756+
}
1757+
1758+
if (v1->V > v2->V)
1759+
{
1760+
vv2 = v1;
1761+
vv1 = v2;
1762+
}
1763+
else
1764+
{
1765+
vv2 = v2;
1766+
vv1 = v1;
1767+
}
1768+
1769+
GSVector2 pos_range(vu2->XYZ.X - vu1->XYZ.X, vv2->XYZ.Y - vv1->XYZ.Y);
1770+
const GSVector2 uv_range(vu2->U - vu1->U, vv2->V - vv1->V);
1771+
if (pos_range.x == 0)
1772+
pos_range.x = 1;
1773+
if (pos_range.y == 0)
1774+
pos_range.y = 1;
1775+
const GSVector2 grad(uv_range / pos_range);
1776+
bool isclamp_w = m_context->CLAMP.WMS > 0 && m_context->CLAMP.WMS < 3;
1777+
bool isclamp_h = m_context->CLAMP.WMT > 0 && m_context->CLAMP.WMT < 3;
1778+
int max_w = (m_context->CLAMP.WMS == 2) ? m_context->CLAMP.MAXU : ((1 << m_context->TEX0.TW) - 1);
1779+
int max_h = (m_context->CLAMP.WMT == 2) ? m_context->CLAMP.MAXV : ((1 << m_context->TEX0.TH) - 1);
1780+
int width = vu2->U >> 4;
1781+
int height = vv2->V >> 4;
1782+
1783+
if (m_context->CLAMP.WMS == 3)
1784+
width = (width & m_context->CLAMP.MINU) | m_context->CLAMP.MAXU;
1785+
1786+
if (m_context->CLAMP.WMT == 3)
1787+
height = (height & m_context->CLAMP.MINV) | m_context->CLAMP.MAXV;
1788+
1789+
if ((isclamp_w && width <= max_w && grad.x != 1.0f) || !isclamp_w)
1790+
{
1791+
if (vu2->U >= 0x1 && (!(vu2->U & 0xf) || grad.x <= 1.0f))
1792+
{
1793+
vu2->U = (vu2->U - 1);
1794+
/*if (!isclamp_w && ((vu2->XYZ.X - m_context->XYOFFSET.OFX) >> 4) < m_context->scissor.in.z)
1795+
vu2->XYZ.X -= 1;*/
1796+
}
1797+
}
1798+
else
1799+
vu2->U &= ~0xf;
1800+
1801+
// Dark Cloud 2 tries to address wider than the TBW when in CLAMP mode (maybe some weird behaviour in HW?)
1802+
if ((vu2->U >> 4) > (int)(m_context->TEX0.TBW * 64) && isclamp_w && (vu2->U >> 4) - 8 <= (int)(m_context->TEX0.TBW * 64))
1803+
{
1804+
vu2->U = (m_context->TEX0.TBW * 64) << 4;
1805+
}
1806+
1807+
if ((isclamp_h && height <= max_h && grad.y != 1.0f) || !isclamp_h)
1808+
{
1809+
if (vv2->V >= 0x1 && (!(vv2->V & 0xf) || grad.y <= 1.0f))
1810+
{
1811+
vv2->V = (vv2->V - 1);
1812+
/*if (!isclamp_h && ((vv2->XYZ.Y - m_context->XYOFFSET.OFY) >> 4) < m_context->scissor.in.w)
1813+
vv2->XYZ.Y -= 1;*/
1814+
}
1815+
}
1816+
else
1817+
vv2->V &= ~0xf;
1818+
}
1819+
}
1820+
}
1821+
else if (!PRIM->FST) // STQ's
17331822
{
17341823
const bool is_sprite = GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS;
17351824
// ST's have the lowest 9 bits (or greater depending on exponent difference) rounding down (from hardware tests).
@@ -1783,7 +1872,6 @@ void GSState::FlushPrim()
17831872
if (unused > 0)
17841873
{
17851874
memcpy(m_vertex.buff, buff, sizeof(GSVertex) * unused);
1786-
17871875
m_vertex.tail = unused;
17881876
m_vertex.next = next > head ? next - head : 0;
17891877

@@ -3727,7 +3815,6 @@ __forceinline void GSState::VertexKick(u32 skip)
37273815
const GSVector4i new_v1(m_v.m[1]);
37283816

37293817
GSVector4i* RESTRICT tailptr = (GSVector4i*)&m_vertex.buff[tail];
3730-
37313818
tailptr[0] = new_v0;
37323819
tailptr[1] = new_v1;
37333820

pcsx2/GameDatabase.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ static const char* s_gs_hw_fix_names[] = {
377377
"estimateTextureRegion",
378378
"PCRTCOffsets",
379379
"PCRTCOverscan",
380+
"preRoundSprites",
380381
"trilinearFiltering",
381382
"skipDrawStart",
382383
"skipDrawEnd",
@@ -423,6 +424,7 @@ bool GameDatabaseSchema::isUserHackHWFix(GSHWFixId id)
423424
case GSHWFixId::Deinterlace:
424425
case GSHWFixId::Mipmap:
425426
case GSHWFixId::TexturePreloading:
427+
case GSHWFixId::PreRoundSprites:
426428
case GSHWFixId::TrilinearFiltering:
427429
case GSHWFixId::MinimumBlendingLevel:
428430
case GSHWFixId::MaximumBlendingLevel:
@@ -779,6 +781,10 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
779781
config.PCRTCOverscan = (value > 0);
780782
break;
781783

784+
case GSHWFixId::PreRoundSprites:
785+
config.PreRoundSprites = (value > 0);
786+
break;
787+
782788
case GSHWFixId::Mipmap:
783789
config.HWMipmap = (value > 0);
784790
break;

pcsx2/GameDatabase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace GameDatabaseSchema
6060
EstimateTextureRegion,
6161
PCRTCOffsets,
6262
PCRTCOverscan,
63+
PreRoundSprites,
6364

6465
// integer settings
6566
TrilinearFiltering,

pcsx2/Pcsx2Config.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ Pcsx2Config::GSOptions::GSOptions()
748748
PreloadFrameWithGSData = false;
749749
Mipmap = true;
750750
HWMipmap = true;
751+
PreRoundSprites = false;
751752

752753
ManualUserHacks = false;
753754
UserHacks_AlignSpriteX = false;
@@ -956,6 +957,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
956957
SettingsWrapBitBool(OsdShowHardwareInfo);
957958
SettingsWrapBitBool(OsdShowVideoCapture);
958959
SettingsWrapBitBool(OsdShowInputRec);
960+
SettingsWrapBitBoolEx(PreRoundSprites, "preround_sprites");
959961

960962
SettingsWrapBitBool(HWSpinGPUForReadbacks);
961963
SettingsWrapBitBool(HWSpinCPUForReadbacks);

0 commit comments

Comments
 (0)