GS/HW: Implement FBW-based moves

Used by Metal Gear Solid 2, and Zone of the Enders 2.
This commit is contained in:
Stenzek 2023-06-17 00:12:54 +10:00 committed by Connor McLaughlin
parent b6923f49b1
commit fc4112c459
2 changed files with 108 additions and 2 deletions

View File

@ -2583,9 +2583,10 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u32 DBW, u32 DPSM, int dx, int dy, int w, int h)
{
if (SBP == DBP && SPSM == DPSM && !GSLocalMemory::m_psm[SPSM].depth && ShuffleMove(SBP, SBW, SPSM, sx, sy, dx, dy, w, h))
{
return true;
}
if (SPSM == DPSM && SBW == 1 && SBW == DBW && PageMove(SBP, DBP, SBW, SPSM, sx, sy, dx, dy, w, h))
return true;
// TODO: In theory we could do channel swapping on the GPU, but we haven't found anything which needs it so far.
if (SPSM != DPSM)
@ -2797,6 +2798,108 @@ bool GSTextureCache::ShuffleMove(u32 BP, u32 BW, u32 PSM, int sx, int sy, int dx
return true;
}
bool GSTextureCache::PageMove(u32 SBP, u32 DBP, u32 BW, u32 PSM, int sx, int sy, int dx, int dy, int w, int h)
{
// Only supports 1-wide at the moment.
pxAssert(BW == 1);
const GSVector4i src = GSVector4i(sx, sy, sx + w, sy + h);
const GSVector4i drc = GSVector4i(dx, dy, dx + w, dy + h);
if (!GSLocalMemory::IsPageAligned(PSM, src) || !GSLocalMemory::IsPageAligned(PSM, drc))
return false;
// How many pages are we dealing with?
const GSVector2i& pgs = GSLocalMemory::m_psm[PSM].pgs;
const u32 num_pages = (h / pgs.y) * BW;
const u32 src_page_offset = ((sy / pgs.y) * BW) + (sx / pgs.x);
const u32 src_block_end = SBP + (((src_page_offset + num_pages) * BLOCKS_PER_PAGE) - 1);
const u32 dst_page_offset = ((dy / pgs.y) * BW) + (dx / pgs.x);
const u32 dst_block_end = DBP + (((dst_page_offset + num_pages) * BLOCKS_PER_PAGE) - 1);
pxAssert(num_pages > 0);
GL_PUSH("GSTextureCache::PageMove(): %u pages, with offset of %u src %u dst", num_pages, src_page_offset,
dst_page_offset);
// Find our targets.
Target* stgt = nullptr;
Target* dtgt = nullptr;
for (int type = 0; type < 2; type++)
{
for (Target* tgt : m_dst[type])
{
// We _could_ do compatible bits here maybe?
if (tgt->m_TEX0.PSM != PSM)
continue;
// Check that the end block is in range. If it's not, we can't do this, and have to fall back to local memory.
const u32 tgt_end = tgt->UnwrappedEndBlock();
if (tgt->m_TEX0.TBP0 <= SBP && src_block_end <= tgt_end)
stgt = tgt;
if (tgt->m_TEX0.TBP0 <= DBP && dst_block_end <= tgt_end)
dtgt = tgt;
if (stgt && dtgt)
break;
}
if (stgt && dtgt)
break;
}
if (!stgt || !dtgt)
{
GL_INS("Targets not found.");
return false;
}
// Double-check that we're not copying to a non-page-aligned target.
if (((SBP - stgt->m_TEX0.TBP0) % BLOCKS_PER_PAGE) != 0 || ((DBP - dtgt->m_TEX0.TBP0) % BLOCKS_PER_PAGE) != 0)
{
GL_INS("Effective SBP of %x or DBP of %x is not page aligned.", SBP - stgt->m_TEX0.TBP0, DBP - dtgt->m_TEX0.TBP0);
return false;
}
// Need to offset based on the target's actual BP.
const u32 real_src_offset = ((SBP - stgt->m_TEX0.TBP0) / BLOCKS_PER_PAGE) + src_page_offset;
const u32 real_dst_offset = ((DBP - dtgt->m_TEX0.TBP0) / BLOCKS_PER_PAGE) + dst_page_offset;
CopyPages(stgt, stgt->m_TEX0.TBW, real_src_offset, dtgt, dtgt->m_TEX0.TBW, real_dst_offset, num_pages);
return true;
}
void GSTextureCache::CopyPages(Target* src, u32 sbw, u32 src_offset, Target* dst, u32 dbw, u32 dst_offset, u32 num_pages, ShaderConvert shader)
{
GL_PUSH("GSTextureCache::CopyPages(): %u pages at %x[eff %x] BW %u to %x[eff %x] BW %u", num_pages,
src->m_TEX0.TBP0, src->m_TEX0.TBP0 + src_offset, sbw, dst->m_TEX0.TBP0, dst->m_TEX0.TBP0 + dst_offset, dbw);
// Create rectangles for the pages.
const GSVector2i& pgs = GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs;
const GSVector4i page_rc = GSVector4i::loadh(pgs);
const GSVector4 src_size = GSVector4(src->GetUnscaledSize()).xyxy();
const GSVector4 dst_scale = GSVector4(dst->GetScale());
GSDevice::MultiStretchRect* rects = static_cast<GSDevice::MultiStretchRect*>(alloca(sizeof(GSDevice::MultiStretchRect) * num_pages));
for (u32 i = 0; i < num_pages; i++)
{
const u32 src_page_num = src_offset + i;
const GSVector2i src_offset = GSVector2i((src_page_num % sbw) * pgs.x, (src_page_num / sbw) * pgs.y);
const u32 dst_page_num = dst_offset + i;
const GSVector2i dst_offset = GSVector2i((dst_page_num % dbw) * pgs.x, (dst_page_num / dbw) * pgs.y);
const GSVector4i src_rect = page_rc + GSVector4i(src_offset).xyxy();
const GSVector4i dst_rect = page_rc + GSVector4i(dst_offset).xyxy();
GL_INS("Copy page %u @ <%d,%d=>%d,%d> to %u @ <%d,%d=>%d,%d>", src_page_num, src_rect.x, src_rect.y, src_rect.z,
src_rect.w, dst_page_num, dst_rect.x, dst_rect.y, dst_rect.z, dst_rect.w);
GSDevice::MultiStretchRect& rc = rects[i];
rc.src = src->m_texture;
rc.src_rect = GSVector4(src_rect) / src_size;
rc.dst_rect = GSVector4(dst_rect) * dst_scale;
rc.linear = false;
rc.wmask.wrgba = 0xf;
}
// No need to sort here, it's all from the same texture.
g_gs_device->DrawMultiStretchRects(rects, num_pages, dst->m_texture, shader);
}
GSTextureCache::Target* GSTextureCache::GetExactTarget(u32 BP, u32 BW, int type, u32 end_bp)
{
auto& rts = m_dst[type];

View File

@ -480,6 +480,9 @@ public:
bool Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u32 DBW, u32 DPSM, int dx, int dy, int w, int h);
bool ShuffleMove(u32 BP, u32 BW, u32 PSM, int sx, int sy, int dx, int dy, int w, int h);
bool PageMove(u32 SBP, u32 DBP, u32 BW, u32 PSM, int sx, int sy, int dx, int dy, int w, int h);
void CopyPages(Target* src, u32 sbw, u32 src_offset, Target* dst, u32 dbw, u32 dst_offset, u32 num_pages,
ShaderConvert shader = ShaderConvert::COPY);
void IncAge();