diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui
index d1b6a9031d..8963a6ef46 100644
--- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui
+++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui
@@ -1055,7 +1055,12 @@
-
- Align To Native
+ Align to Native
+
+
+ -
+
+ Align to Native - with Texture Offset
diff --git a/pcsx2/Config.h b/pcsx2/Config.h
index bca65de381..00da38c6a6 100644
--- a/pcsx2/Config.h
+++ b/pcsx2/Config.h
@@ -440,6 +440,7 @@ enum class GSHalfPixelOffset : u8
Special,
SpecialAggressive,
Native,
+ NativeWTexOffset,
MaxCount
};
diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
index e31182a729..0c041317be 100644
--- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
+++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
@@ -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);
@@ -5160,6 +5160,28 @@ __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)
+ {
+ if (PRIM->FST)
+ {
+ m_conf.cb_vs.texture_offset.x = 6.0f + (0.25f * tex->m_scale);
+ m_conf.cb_vs.texture_offset.y = 6.0f + (0.25f * tex->m_scale);
+ }
+ else
+ {
+ const GSVertex* v = &m_vertex.buff[0];
+ const float tw = static_cast(1 << m_cached_ctx.TEX0.TW);
+ const float th = static_cast(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;
+ }
+ }
+ }
}
else if (tex->m_target)
{
@@ -5211,6 +5233,28 @@ __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)
+ {
+ if (PRIM->FST)
+ {
+ m_conf.cb_vs.texture_offset.x = 6.0f + (0.25f * tex->m_scale);
+ m_conf.cb_vs.texture_offset.y = 6.0f + (0.25f * tex->m_scale);
+ }
+ else
+ {
+ const GSVertex* v = &m_vertex.buff[0];
+ const float tw = static_cast(1 << m_cached_ctx.TEX0.TW);
+ const float th = static_cast(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();
@@ -5540,7 +5584,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(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;
@@ -6272,7 +6316,8 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
float sx, sy, ox2, oy2;
const float ox = static_cast(static_cast(m_context->XYOFFSET.OFX));
const float oy = static_cast(static_cast(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);
@@ -6301,8 +6346,16 @@ __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 ((!tex || !tex->m_from_target || tex->m_scale > 1.0f) && GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::NativeWTexOffset)
+ {
+ ox2 = (-1.0f / (unscaled_x * rtscale));
+ oy2 = (-1.0f / (unscaled_y * rtscale));
+ }
+ else
+ {
+ ox2 = -1.0f / unscaled_x;
+ oy2 = -1.0f / unscaled_y;
+ }
}
m_conf.cb_vs.vertex_scale = GSVector2(sx, sy);
diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp
index ffcc7b1416..c1ea068166 100644
--- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp
+++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp
@@ -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);
diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp
index d5af235c4e..b42512d6aa 100644
--- a/pcsx2/ImGui/FullscreenUI.cpp
+++ b/pcsx2/ImGui/FullscreenUI.cpp
@@ -3824,7 +3824,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)"),