From f84425b67cf97aea1b5c363fc89eca4b69a28797 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sun, 23 Feb 2025 19:19:34 +0000 Subject: [PATCH] GS/HW: Add new HPO - Align to Native With Texture Offset --- pcsx2-qt/Settings/GraphicsSettingsWidget.ui | 7 ++- pcsx2/Config.h | 1 + pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 63 +++++++++++++++++++-- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 4 +- pcsx2/ImGui/FullscreenUI.cpp | 3 +- 5 files changed, 69 insertions(+), 9 deletions(-) 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)"),