GS/HW: Disable HPO/even sprite position on texture shuffles.

Quad conversion in texture shuffles is already aligned and not intended to interact with upscaling coord fixes.
This commit is contained in:
TJnotJT
2026-04-11 08:29:58 -04:00
committed by lightningterror
parent 06616ec98e
commit fce3be8544
+29 -13
View File
@@ -861,7 +861,8 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
const bool bilinear = m_vt.IsRealLinear();
// Copy the attributes from the provoking vertex.
GSVertex v_default = primclass == GS_SPRITE_CLASS ? m_vertex.buff[1] : m_vertex.buff[2];
GSVertex v_default = primclass == GS_SPRITE_CLASS ? m_vertex.buff[m_index.buff[1]] :
m_vertex.buff[m_index.buff[2]];
const auto SetTexCoords = [&](float u, float v, GSVertex& vtx_out) {
if constexpr (fst)
@@ -882,11 +883,15 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
vtx_out.XYZ.Y = xyof.y + static_cast<u32>(y * 16.0f);
};
const auto WriteQuad = [&](const GSVector4& xy, GSVector4 uv, GSVertex*& vout, u16*& iout) {
const auto WriteQuad = [&](const GSVector4i& xyi, const GSVector4i& uvi, GSVertex*& vout, u16*& iout) {
GSVector4 xy(xyi);
GSVector4 uv(uvi);
if (bilinear)
{
// Translate to texel center for bilinear.
uv += GSVector4(0.5f);
GL_INS("HW: Translate to texel center for bilinear.");
uv += GSVector4(0.5f) / rt->GetScale();
}
if constexpr (primclass == GS_SPRITE_CLASS)
@@ -982,7 +987,7 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
GL_PUSH("HW: Converting to single quad for texture shuffle.");
GL_INS("Before adjustment: pos={%d, %d, %d, %d}, tex={%d, %d, %d, %d}, scissor={%d, %d, %d, %d}",
GL_INS("HW: Before adjustment: pos={%d, %d, %d, %d}, tex={%d, %d, %d, %d}, scissor={%d, %d, %d, %d}",
m_r.x ,m_r.y, m_r.z, m_r.w, tex_r.x, tex_r.y, tex_r.z, tex_r.w,
scissor_r.x, scissor_r.y, scissor_r.z, scissor_r.w);
@@ -994,6 +999,7 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
if (m_texture_shuffle.real_16_bit_source)
{
GL_INS("HW: Real 16 bit source: no tex coord change.");
half_u = false;
half_v = false;
}
@@ -1002,6 +1008,7 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
m_texture_shuffle.type == TextureShuffleType::TwoPixel ||
m_texture_shuffle.type == TextureShuffleType::HackShuffle)
{
GL_INS("HW: Split shuffle/TwoPixel/HackShuffle: no pos or tex coord change.");
half_x = false;
half_y = false;
half_u = false;
@@ -1012,6 +1019,8 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
// Currently, this shuffle is done with the NFS CRC hack.
// It is a split texture shuffle, but the draws are skipped with the hack instead of
// being combined in the usual way, so we need to adjust the draw rect here.
GL_INS("HW: GappedSwizzle (NFS Undercover): rewriting rects to use full area.");
half_x = false;
half_y = false;
half_u = false;
@@ -1020,12 +1029,12 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
m_r = rt->GetUnscaledRect();
tex_r = tex->GetUnscaledRect();
scissor_r = m_r;
GL_INS("GappedSwizzle (NFS Undercover): rewriting rects to use full area.");
}
else if (m_texture_shuffle.type == TextureShuffleType::Swizzle ||
m_texture_shuffle.type == TextureShuffleType::SwizzleTex32)
{
GL_INS("HW: Swizzle/SwizzleTex32: no tex coord change.");
if (m_cached_ctx.FRAME.FBW == rt->m_TEX0.TBW * 2)
{
half_y = false;
@@ -1044,6 +1053,7 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
// No super source of truth here, since the width can get batted around, the valid is probably our best bet.
// Dogs will reuse the Z in a different size format for a completely unrelated draw with an FBW of 2,
// then go back to using it in full width.
GL_INS("HW: Non-recursive draw, complex case.");
const bool tex_tbw_is_wrong =
tex->m_target &&
@@ -1131,6 +1141,7 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
else
{
// Handle recursive draws.
GL_INS("HW: Recursive draw, complex case.");
const GSVector2i& frame_pgs = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs;
@@ -1149,7 +1160,7 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
}
}
GL_INS("Halving: X: %d, Y: %d, U: %d, V: %d", half_x, half_y, half_u, half_v);
GL_INS("HW: Halving: X: %d, Y: %d, U: %d, V: %d", half_x, half_y, half_u, half_v);
if (half_x)
{
@@ -1205,7 +1216,7 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
GL_CACHE("HW: Half width/double height shuffle detected, BW changed to %d", m_cached_ctx.FRAME.FBW);
}
GL_INS("After adjustment: pos={%d, %d, %d, %d}, tex={%d, %d, %d, %d}, scissor={%d, %d, %d, %d}",
GL_INS("HW: After adjustment: pos={%d, %d, %d, %d}, tex={%d, %d, %d, %d}, scissor={%d, %d, %d, %d}",
m_r.x, m_r.y, m_r.z, m_r.w, tex_r.x, tex_r.y, tex_r.z, tex_r.w,
scissor_r.x, scissor_r.y, scissor_r.z, scissor_r.w);
@@ -1227,7 +1238,7 @@ void GSRendererHW::ConvertSpriteTextureShuffleImpl(GSTextureCache::Target* rt, G
GSVertex* vout = m_vertex.buff;
u16* iout = m_index.buff;
WriteQuad(GSVector4(m_r), GSVector4(tex_r), vout, iout);
WriteQuad(m_r, tex_r, vout, iout);
m_index.tail = iout - m_index.buff;
m_vertex.head = m_vertex.tail = m_vertex.next = vout - m_vertex.buff;
@@ -1267,7 +1278,8 @@ GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Sou
{
if (GSConfig.UserHacks_HalfPixelOffset <= GSHalfPixelOffset::Normal ||
GSConfig.UserHacks_HalfPixelOffset >= GSHalfPixelOffset::Native ||
GetUpscaleMultiplier() == 1.0f || m_downscale_source || tex->GetScale() == 1.0f)
GetUpscaleMultiplier() == 1.0f || m_downscale_source || tex->GetScale() == 1.0f ||
m_texture_shuffle) // Do not apply HPO on texture shuffles as it already aligns the coordinates.
{
return GSVector4(0.0f);
}
@@ -5359,7 +5371,9 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
{
GL_PUSH("HW: IA");
if (GSConfig.UserHacks_ForceEvenSpritePosition && !m_isPackedUV_HackFlag && m_process_texture && PRIM->FST)
// Do not force even sprite positive for texture shuffles since the coordinates are already aligned.
if (GSConfig.UserHacks_ForceEvenSpritePosition && !m_isPackedUV_HackFlag && m_process_texture && PRIM->FST &&
!m_texture_shuffle)
{
for (u32 i = 0; i < m_vertex.next; i++)
m_vertex.buff[i].UV &= 0x3FEF3FEF;
@@ -5986,7 +6000,8 @@ void GSRendererHW::DetermineVSConfig(GSTextureCache::Target* rt, float rtscale,
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)
// Do not apply HPO on texture shuffle draws, as the coordinates are already aligned.
if ((GSConfig.UserHacks_HalfPixelOffset < GSHalfPixelOffset::Native || m_texture_shuffle) && rtscale > 1.0f)
{
sx = 2.0f * rtscale / (rtsize.x << 4);
sy = 2.0f * rtscale / (rtsize.y << 4);
@@ -7520,7 +7535,8 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
// 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)
// Do not apply HPO on texture shuffles as it already aligns the coordinates.
if (GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::NativeWTexOffset && !m_texture_shuffle)
{
const u32 psm = rt ? rt->m_TEX0.PSM : ds->m_TEX0.PSM;
const bool can_offset = m_r.width() > GSLocalMemory::m_psm[psm].pgs.x || m_r.height() > GSLocalMemory::m_psm[psm].pgs.y;