GS/HW: Add 'Align To Native' HPO mode

This commit is contained in:
Stenzek 2023-12-02 14:24:15 +10:00 committed by Connor McLaughlin
parent e2dcabcbea
commit 5338a4f17c
8 changed files with 69 additions and 32 deletions

View File

@ -1104,6 +1104,11 @@
<string>Special (Texture - Aggressive)</string>
</property>
</item>
<item>
<property name="text">
<string>Align To Native</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">

View File

@ -377,6 +377,16 @@ enum class GSBilinearDirtyMode : u8
MaxCount
};
enum class GSHalfPixelOffset : u8
{
Off,
Normal,
Special,
SpecialAggressive,
Native,
MaxCount
};
// Template function for casting enumerations to their underlying type
template <typename Enumeration>
typename std::underlying_type<Enumeration>::type enum_cast(Enumeration E)
@ -761,7 +771,7 @@ struct Pcsx2Config
int SkipDrawEnd = 0;
GSHWAutoFlushLevel UserHacks_AutoFlush = GSHWAutoFlushLevel::Disabled;
s8 UserHacks_HalfPixelOffset = 0;
GSHalfPixelOffset UserHacks_HalfPixelOffset = GSHalfPixelOffset::Off;
s8 UserHacks_RoundSprite = 0;
s32 UserHacks_TCOffsetX = 0;
s32 UserHacks_TCOffsetY = 0;

View File

@ -270,7 +270,7 @@ float GSRenderer::GetModXYOffset()
{
float mod_xy = 0.0f;
if (GSConfig.UserHacks_HalfPixelOffset == 1)
if (GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::Normal)
{
mod_xy = GetUpscaleMultiplier();
switch (static_cast<int>(std::round(mod_xy)))

View File

@ -593,8 +593,12 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, GS
GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Source* tex)
{
if (GSConfig.UserHacks_HalfPixelOffset <= 1 || GetUpscaleMultiplier() == 1.0f)
if (GSConfig.UserHacks_HalfPixelOffset <= GSHalfPixelOffset::Normal ||
GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::Native ||
GetUpscaleMultiplier() == 1.0f)
{
return GSVector4(0.0f);
}
const GSVertex* v = &m_vertex.buff[0];
const float scale = tex->GetScale();
@ -607,7 +611,7 @@ GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Sou
if (PRIM->FST)
{
if (GSConfig.UserHacks_HalfPixelOffset == 3)
if (GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::SpecialAggressive)
{
if (!linear && t_position == 8)
{
@ -5154,30 +5158,43 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.vs.fst = PRIM->FST;
// FIXME D3D11 and GL support half pixel center. Code could be easier!!!
const GSVector2i rtsize = m_conf.ds ? m_conf.ds->GetSize() : m_conf.rt->GetSize();
const float rtscale = (ds ? ds->GetScale() : rt->GetScale());
const float sx = 2.0f * rtscale / (rtsize.x << 4);
const float sy = 2.0f * rtscale / (rtsize.y << 4);
const float ox = static_cast<float>(static_cast<int>(m_context->XYOFFSET.OFX));
const float oy = static_cast<float>(static_cast<int>(m_context->XYOFFSET.OFY));
float ox2 = -1.0f / rtsize.x;
float oy2 = -1.0f / rtsize.y;
float mod_xy = 0.0f;
//This hack subtracts around half a pixel from OFX and OFY.
//
//The resulting shifted output aligns better with common blending / corona / blurring effects,
//but introduces a few bad pixels on the edges.
if (!rt)
const GSTextureCache::Target* rt_or_ds = rt ? rt : ds;
const GSVector2i rtsize = rt_or_ds->GetTexture()->GetSize();
const float rtscale = rt_or_ds->GetScale();
float sx, sy, ox, oy, ox2, oy2;
if (GSConfig.UserHacks_HalfPixelOffset != GSHalfPixelOffset::Native)
{
mod_xy = GetModXYOffset();
sx = 2.0f * rtscale / (rtsize.x << 4);
sy = 2.0f * rtscale / (rtsize.y << 4);
ox = static_cast<float>(static_cast<int>(m_context->XYOFFSET.OFX));
oy = static_cast<float>(static_cast<int>(m_context->XYOFFSET.OFY));
ox2 = -1.0f / rtsize.x;
oy2 = -1.0f / rtsize.y;
float mod_xy = 0.0f;
//This hack subtracts around half a pixel from OFX and OFY.
//
//The resulting shifted output aligns better with common blending / corona / blurring effects,
//but introduces a few bad pixels on the edges.
if (!rt)
mod_xy = GetModXYOffset();
else
mod_xy = rt->OffsetHack_modxy;
if (mod_xy > 1.0f)
{
ox2 *= mod_xy;
oy2 *= mod_xy;
}
}
else
mod_xy = rt->OffsetHack_modxy;
if (mod_xy > 1.0f)
{
ox2 *= mod_xy;
oy2 *= mod_xy;
// Align coordinates to native resolution framebuffer, hope for the best.
sx = 2.0f / (rt_or_ds->GetUnscaledWidth() << 4);
sy = 2.0f / (rt_or_ds->GetUnscaledHeight() << 4);
ox = static_cast<float>(static_cast<int>(m_context->XYOFFSET.OFX));
oy = static_cast<float>(static_cast<int>(m_context->XYOFFSET.OFY));
ox2 = -1.0f / rt_or_ds->GetUnscaledWidth();
oy2 = -1.0f / rt_or_ds->GetUnscaledHeight();
}
m_conf.cb_vs.vertex_scale = GSVector2(sx, sy);

View File

@ -606,7 +606,7 @@ bool GameDatabaseSchema::GameEntry::configMatchesHWFix(const Pcsx2Config::GSOpti
return (config.SkipDrawEnd == value);
case GSHWFixId::HalfPixelOffset:
return (config.UpscaleMultiplier <= 1.0f || config.UserHacks_HalfPixelOffset == value);
return (config.UpscaleMultiplier <= 1.0f || config.UserHacks_HalfPixelOffset == static_cast<GSHalfPixelOffset>(value));
case GSHWFixId::RoundSprite:
return (config.UpscaleMultiplier <= 1.0f || config.UserHacks_RoundSprite == value);
@ -780,8 +780,11 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
break;
case GSHWFixId::HalfPixelOffset:
config.UserHacks_HalfPixelOffset = value;
break;
{
if (value >= 0 && value < static_cast<int>(GSHalfPixelOffset::MaxCount))
config.UserHacks_HalfPixelOffset = static_cast<GSHalfPixelOffset>(value);
}
break;
case GSHWFixId::RoundSprite:
config.UserHacks_RoundSprite = value;

View File

@ -3314,6 +3314,7 @@ void FullscreenUI::DrawGraphicsSettingsPage()
FSUI_NSTR("Normal (Vertex)"),
FSUI_NSTR("Special (Texture)"),
FSUI_NSTR("Special (Texture - Aggressive)"),
FSUI_NSTR("Align To Native"),
};
static constexpr const char* s_round_sprite_options[] = {
FSUI_NSTR("Off (Default)"),
@ -7034,6 +7035,7 @@ TRANSLATE_NOOP("FullscreenUI", "Merge Targets");
TRANSLATE_NOOP("FullscreenUI", "Normal (Vertex)");
TRANSLATE_NOOP("FullscreenUI", "Special (Texture)");
TRANSLATE_NOOP("FullscreenUI", "Special (Texture - Aggressive)");
TRANSLATE_NOOP("FullscreenUI", "Align To Native");
TRANSLATE_NOOP("FullscreenUI", "Half");
TRANSLATE_NOOP("FullscreenUI", "Force Bilinear");
TRANSLATE_NOOP("FullscreenUI", "Force Nearest");

View File

@ -391,8 +391,8 @@ void ImGuiManager::DrawSettingsOverlay()
APPEND("AF={} ", EmuConfig.GS.MaxAnisotropy);
if (GSConfig.Dithering != 2)
APPEND("DI={} ", GSConfig.Dithering);
if (GSConfig.UserHacks_HalfPixelOffset > 0)
APPEND("HPO={} ", GSConfig.UserHacks_HalfPixelOffset);
if (GSConfig.UserHacks_HalfPixelOffset != GSHalfPixelOffset::Off)
APPEND("HPO={} ", static_cast<u32>(GSConfig.UserHacks_HalfPixelOffset));
if (GSConfig.UserHacks_RoundSprite > 0)
APPEND("RS={} ", GSConfig.UserHacks_RoundSprite);
if (GSConfig.UserHacks_TCOffsetX != 0 || GSConfig.UserHacks_TCOffsetY != 0)

View File

@ -805,7 +805,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
GSSettingIntEx(SkipDrawEnd, "UserHacks_SkipDraw_End");
SkipDrawEnd = std::max(SkipDrawStart, SkipDrawEnd);
GSSettingIntEx(UserHacks_HalfPixelOffset, "UserHacks_HalfPixelOffset");
GSSettingIntEnumEx(UserHacks_HalfPixelOffset, "UserHacks_HalfPixelOffset");
GSSettingIntEx(UserHacks_RoundSprite, "UserHacks_round_sprite_offset");
GSSettingIntEx(UserHacks_TCOffsetX, "UserHacks_TCOffsetX");
GSSettingIntEx(UserHacks_TCOffsetY, "UserHacks_TCOffsetY");
@ -872,7 +872,7 @@ void Pcsx2Config::GSOptions::MaskUserHacks()
UserHacks_NativePaletteDraw = false;
UserHacks_DisableSafeFeatures = false;
UserHacks_DisableRenderFixes = false;
UserHacks_HalfPixelOffset = 0;
UserHacks_HalfPixelOffset = GSHalfPixelOffset::Off;
UserHacks_RoundSprite = 0;
UserHacks_AutoFlush = GSHWAutoFlushLevel::Disabled;
PreloadFrameWithGSData = false;
@ -903,7 +903,7 @@ void Pcsx2Config::GSOptions::MaskUpscalingHacks()
UserHacks_WildHack = false;
UserHacks_BilinearHack = GSBilinearDirtyMode::Automatic;
UserHacks_NativePaletteDraw = false;
UserHacks_HalfPixelOffset = 0;
UserHacks_HalfPixelOffset = GSHalfPixelOffset::Off;
UserHacks_RoundSprite = 0;
UserHacks_TCOffsetX = 0;
UserHacks_TCOffsetY = 0;