Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GS/HW: Add new HPO - Align to Native With Texture Offset #12350

Merged
merged 2 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion pcsx2-qt/Settings/GraphicsSettingsWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,12 @@
</item>
<item>
<property name="text">
<string>Align To Native</string>
<string>Align to Native</string>
</property>
</item>
<item>
<property name="text">
<string>Align to Native - with Texture Offset</string>
</property>
</item>
</widget>
Expand Down
1 change: 1 addition & 0 deletions pcsx2/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ enum class GSHalfPixelOffset : u8
Special,
SpecialAggressive,
Native,
NativeWTexOffset,
MaxCount
};

Expand Down
81 changes: 76 additions & 5 deletions pcsx2/GS/Renderers/HW/GSRendererHW.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba,
GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Source* tex)
{
if (GSConfig.UserHacks_HalfPixelOffset <= GSHalfPixelOffset::Normal ||
GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::Native ||
GSConfig.UserHacks_HalfPixelOffset >= GSHalfPixelOffset::Native ||
GetUpscaleMultiplier() == 1.0f || m_downscale_source || tex->GetScale() == 1.0f)
{
return GSVector4(0.0f);
Expand Down Expand Up @@ -5160,6 +5160,26 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,

const GSVector4 half_pixel = RealignTargetTextureCoordinate(tex);
m_conf.cb_vs.texture_offset = GSVector2(half_pixel.x, half_pixel.y);

// Can be seen with the cabin part of the ship in God of War, offsets are required when using FST.
// ST uses a normalized position so doesn't need an offset here, will break Bionicle Heroes.
if (GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::NativeWTexOffset)
{
if (!m_downscale_source && tex->m_scale > 1.0f)
{
const GSVertex* v = &m_vertex.buff[0];
if (PRIM->FST)
{
const int x1_frac = ((v[1].XYZ.X - m_context->XYOFFSET.OFX) & 0xf);
const int y1_frac = ((v[1].XYZ.Y - m_context->XYOFFSET.OFY) & 0xf);

if (!(x1_frac & 8))
m_conf.cb_vs.texture_offset.x = 6.0f + (0.25f * tex->m_scale);
if (!(y1_frac & 8))
m_conf.cb_vs.texture_offset.y = 6.0f + (0.25f * tex->m_scale);
}
}
}
}
else if (tex->m_target)
{
Expand Down Expand Up @@ -5211,6 +5231,33 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
const GSVector4 half_pixel = RealignTargetTextureCoordinate(tex);
m_conf.cb_vs.texture_offset = GSVector2(half_pixel.x, half_pixel.y);

if (GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::NativeWTexOffset)
{
if (!m_downscale_source && tex->m_scale > 1.0f)
{
const GSVertex* v = &m_vertex.buff[0];
if (PRIM->FST)
{
const int x1_frac = ((v[1].XYZ.X - m_context->XYOFFSET.OFX) & 0xf);
const int y1_frac = ((v[1].XYZ.Y - m_context->XYOFFSET.OFY) & 0xf);

if (!(x1_frac & 8))
m_conf.cb_vs.texture_offset.x = 6.0f + (0.25f * tex->m_scale);
if (!(y1_frac & 8))
m_conf.cb_vs.texture_offset.y = 6.0f + (0.25f * tex->m_scale);
}
else
{
const float tw = static_cast<float>(1 << m_cached_ctx.TEX0.TW);
const float th = static_cast<float>(1 << m_cached_ctx.TEX0.TH);
const float q = v[0].RGBAQ.Q;

m_conf.cb_vs.texture_offset.x = 0.5f * q / tw;
m_conf.cb_vs.texture_offset.y = 0.5f * q / th;
}
}
}

if (m_vt.m_primclass == GS_SPRITE_CLASS && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal > 0 && m_index.tail >= 4)
{
HandleManualDeswizzle();
Expand Down Expand Up @@ -5540,7 +5587,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c
// When using native HPO, the top-left column/row of pixels are often not drawn. Clamp these away to avoid sampling black,
// causing bleeding into the edges of the downsampled texture.
const u32 downsample_factor = static_cast<u32>(src_target->GetScale());
const GSVector2i clamp_min = (GSConfig.UserHacks_HalfPixelOffset != GSHalfPixelOffset::Native) ?
const GSVector2i clamp_min = (GSConfig.UserHacks_HalfPixelOffset < GSHalfPixelOffset::Native) ?
GSVector2i(0, 0) :
GSVector2i(downsample_factor, downsample_factor);
GSVector4i copy_rect = tmm.coverage;
Expand Down Expand Up @@ -6272,7 +6319,8 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
float sx, sy, ox2, oy2;
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));
if (GSConfig.UserHacks_HalfPixelOffset != GSHalfPixelOffset::Native && rtscale > 1.0f)

if ((GSConfig.UserHacks_HalfPixelOffset < GSHalfPixelOffset::Native || m_channel_shuffle) && rtscale > 1.0f)
{
sx = 2.0f * rtscale / (rtsize.x << 4);
sy = 2.0f * rtscale / (rtsize.y << 4);
Expand Down Expand Up @@ -6301,8 +6349,31 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
const int unscaled_y = rt_or_ds ? rt_or_ds->GetUnscaledHeight() : 0;
sx = 2.0f / (unscaled_x << 4);
sy = 2.0f / (unscaled_y << 4);
ox2 = -1.0f / unscaled_x;
oy2 = -1.0f / unscaled_y;

if (GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::NativeWTexOffset)
{
ox2 = (-1.0f / (unscaled_x * rtscale));
oy2 = (-1.0f / (unscaled_y * rtscale));

// Having the vertex negatively offset is a common thing for copying sprites but this causes problems when upscaling, so we need to further adjust the offset.
// This kinda screws things up when using ST, so let's not.
if (m_vt.m_primclass == GS_SPRITE_CLASS && rtscale > 1.0f && (!tex || PRIM->FST))
{
const GSVertex* v = &m_vertex.buff[0];
const int x1_frac = ((v[1].XYZ.X - m_context->XYOFFSET.OFX) & 0xf);
const int y1_frac = ((v[1].XYZ.Y - m_context->XYOFFSET.OFY) & 0xf);
if (x1_frac & 8)
ox2 *= 1.0f + ((static_cast<float>(16 - x1_frac) / 8.0f) * rtscale);

if (y1_frac & 8)
oy2 *= 1.0f + ((static_cast<float>(16 - y1_frac) / 8.0f) * rtscale);
}
}
else
{
ox2 = -1.0f / unscaled_x;
oy2 = -1.0f / unscaled_y;
}
}

m_conf.cb_vs.vertex_scale = GSVector2(sx, sy);
Expand Down
4 changes: 2 additions & 2 deletions pcsx2/GS/Renderers/HW/GSTextureCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2132,8 +2132,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// And invalidate the target, we're drawing over it so we don't care what's there.
// We can't do this when upscaling, because of the vertex offset, the top/left rows often aren't drawn.
GL_INS("TC: Invalidating%s target %s[%x] because it's completely overwritten.", to_string(type),
(scale > 1.0f && GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::Native) ? "[clearing] " : "", dst->m_TEX0.TBP0);
if (scale > 1.0f && GSConfig.UserHacks_HalfPixelOffset != GSHalfPixelOffset::Native)
(scale > 1.0f && GSConfig.UserHacks_HalfPixelOffset >= GSHalfPixelOffset::Native) ? "[clearing] " : "", dst->m_TEX0.TBP0);
if (scale > 1.0f && GSConfig.UserHacks_HalfPixelOffset < GSHalfPixelOffset::Native)
{
if (dst->m_type == RenderTarget)
g_gs_device->ClearRenderTarget(dst->m_texture, 0);
Expand Down
3 changes: 2 additions & 1 deletion pcsx2/ImGui/FullscreenUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3809,7 +3809,8 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
FSUI_NSTR("Normal (Vertex)"),
FSUI_NSTR("Special (Texture)"),
FSUI_NSTR("Special (Texture - Aggressive)"),
FSUI_NSTR("Align To Native"),
FSUI_NSTR("Align to Native"),
FSUI_NSTR("Align to Native - with Texture Offset"),
};
static constexpr const char* s_native_scaling_options[] = {
FSUI_NSTR("Normal (Default)"),
Expand Down