GS/HW: Carefully allow move to create new targets

Xenosaga I does a move from BP 1C00 to E00, then from E00 to 2A00 a few
frames later for its cutscene transitions. 2A00 then gets used as a
texture and blended on top of the later frames. Because there's no
target at 2A00, the move falls back to the CPU, and E00 contains junk
which gets moved and eventually preloaded instead.

Gradius V uses moves for a screen move/wobble-like effect, by moving
chunks of the framebuffer out, then using those as a texture. It's not
broken at the moment, but it does readback (slow), and break upscaling.
This commit is contained in:
Stenzek 2023-02-11 13:26:46 +10:00 committed by refractionpcsx2
parent 06db8eec48
commit 80dce398e0
1 changed files with 18 additions and 0 deletions

View File

@ -1579,6 +1579,24 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
// Look for an exact match on the targets.
GSTextureCache::Target* src = GetExactTarget(SBP, SBW, SPSM);
GSTextureCache::Target* dst = GetExactTarget(DBP, DBW, DPSM);
// Beware of the case where a game might create a larger texture by moving a bunch of chunks around.
// We use dx/dy == 0 and the TBW check as a safeguard to make sure these go through to local memory.
// Good test case for this is the Xenosaga I cutscene transitions, or Gradius V.
if (src && !dst && dx == 0 && dy == 0 && ((static_cast<u32>(w) + 63) / 64) == DBW)
{
GIFRegTEX0 new_TEX0 = {};
new_TEX0.TBP0 = DBP;
new_TEX0.TBW = DBW;
new_TEX0.PSM = DPSM;
const int real_height = GetTargetHeight(DBP, DBW, DPSM, h);
const GSVector2 scale(src->m_texture->GetScale());
dst = LookupTarget(new_TEX0, GSVector2i(static_cast<int>(w * scale.x), static_cast<int>(real_height * scale.y)), src->m_type, true);
if (dst)
dst->m_texture->SetScale(scale);
}
if (!src || !dst || src->m_texture->GetScale() != dst->m_texture->GetScale())
return false;