GS-TC: Remove uses of Surface Offsets where possible.

This commit is contained in:
refractionpcsx2 2023-03-01 13:22:41 +00:00
parent 5cbcf706e9
commit 56022a9af3
4 changed files with 342 additions and 113 deletions

View File

@ -576,7 +576,7 @@ bool GSLocalMemory::IsPageAligned(u32 bp, u32 spsm, GSVector4i r, bool bppbw_mat
// If the BPP and BW matches, then just make sure it's starting on the edge of a page.
if (bppbw_match)
{
return bp_page_aligned_bp && masked_rect.xyxy().eq(r.xyxy());
return bp_page_aligned_bp;
}
else
{
@ -584,7 +584,7 @@ bool GSLocalMemory::IsPageAligned(u32 bp, u32 spsm, GSVector4i r, bool bppbw_mat
}
}
GSVector4i GSLocalMemory::TranslateAlignedRectByPage(u32 sbp, u32 spsm, GSVector4i src_r, u32 dbp, u32 dpsm, u32 bw)
GSVector4i GSLocalMemory::TranslateAlignedRectByPage(u32 sbp, u32 spsm, u32 sbw, GSVector4i src_r, u32 dbp, u32 dpsm, u32 bw, bool is_invalidation)
{
const GSVector2i src_page_size = m_psm[spsm].pgs;
const GSVector2i dst_page_size = m_psm[dpsm].pgs;
@ -597,7 +597,74 @@ GSVector4i GSLocalMemory::TranslateAlignedRectByPage(u32 sbp, u32 spsm, GSVector
// If they match, we can cheat and just offset the rect by the number of pages.
if (m_psm[spsm].bpp == m_psm[dpsm].bpp)
{
new_rect = src_r;
if (sbw != bw)
{
if (sbw == 0)
{
// BW == 0 loops vertically on the first page. So just copy the whole page vertically.
if (src_r.z > dst_page_size.x)
{
new_rect.x = 0;
new_rect.z = (dst_page_size.x);
}
else
{
new_rect.x = src_r.x;
new_rect.z = src_r.z;
}
if (src_r.w > dst_page_size.y)
{
new_rect.y = 0;
new_rect.w = dst_page_size.y;
}
else
{
new_rect.y = src_r.y;
new_rect.w = src_r.w;
}
}
else
{
u32 start_page = (src_r.y / dst_page_size.y) / bw;
u32 num_pages = ((src_r.w - src_r.y) / dst_page_size.y);
// Round it up to the next row up, Armored Core 3 does SBW = 1 64x1024 on BW= 10 which is 3.2 pages wide.
u32 rows = ((num_pages + (bw - 1)) / bw);
if(((horizontal_offset / dst_page_size.x) + num_pages) == bw)
{
rows += 1;
}
// This is going to wrap, start offset and be overall messy, so we can't use this texture. (FIFA 2005)
// For invalidation, over invalidating isn't *so* bad, plus the game will likely fill in the gaps.
if (!is_invalidation && (((horizontal_offset / dst_page_size.x) + (rect_pages.z * rect_pages.w)) % bw) != 0 && bw > sbw && rows > 1)
return GSVector4i::zero();
if (rows > 1)
{
new_rect.x = 0;
new_rect.y = start_page + (src_r.y & (dst_page_size.y - 1));
new_rect.z = (bw * 64);
new_rect.w = (rows * dst_page_size.y) + (src_r.w & (dst_page_size.y - 1));
}
else
{
new_rect.x = (src_r.x & (dst_page_size.x - 1));
new_rect.z = std::max(num_pages * dst_page_size.x, static_cast<u32>(src_r.z));
if (src_r.w > dst_page_size.y)
{
new_rect.y = 0;
new_rect.w = dst_page_size.y;
}
else
{
new_rect.y = src_r.y;
new_rect.w = std::min(dst_page_size.y, src_r.w);
}
}
}
}
else
new_rect = src_r;
}
else
{
@ -606,6 +673,12 @@ GSVector4i GSLocalMemory::TranslateAlignedRectByPage(u32 sbp, u32 spsm, GSVector
new_rect = (new_rect + GSVector4i(0, vertical_offset).xyxy()).max_i32(GSVector4i(0));
new_rect = (new_rect + GSVector4i(horizontal_offset, 0).xyxy()).max_i32(GSVector4i(0));
if (new_rect.z > (bw * dst_page_size.x))
{
new_rect.z = (bw * dst_page_size.x);
new_rect.w += dst_page_size.y;
}
return new_rect;
}

View File

@ -1135,7 +1135,7 @@ public:
void SaveBMP(const std::string& fn, u32 bp, u32 bw, u32 psm, int w, int h);
static bool IsPageAligned(u32 bp, u32 spsm, GSVector4i r, bool bp_match);
static GSVector4i TranslateAlignedRectByPage(u32 sbp, u32 spsm, GSVector4i src_r, u32 dbp, u32 dpsm, u32 bw);
static GSVector4i TranslateAlignedRectByPage(u32 sbp, u32 spsm, u32 sbw, GSVector4i src_r, u32 dbp, u32 dpsm, u32 bw, bool is_invalidation = false);
};
constexpr inline GSOffset GSOffset::fromKnownPSM(u32 bp, u32 bw, GS_PSM psm)

View File

@ -1546,7 +1546,7 @@ void GSRendererHW::Draw()
{
// Force enable preloading if any of the existing data is needed.
// e.g. NFSMW only writes the alpha channel, and needs the RGB preloaded.
if (((fm & fm_mask) != fm_mask) || // Some channels masked
if ((fm & fm_mask) != 0 && ((fm & fm_mask) != fm_mask) || // Some channels masked
!IsOpaque()) // Blending enabled
{
GL_INS("Forcing preload due to partial/blended CLUT draw");

View File

@ -412,9 +412,25 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
// Surface offsets translates it to 0, 128 -> 128, 128, not 0, 0 -> 128, 128.
if (bp > t->m_TEX0.TBP0)
{
if (page_aligned && bpp_match && bw == t->m_TEX0.TBW)
const GSVector2i page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs;
const bool rect_in_format = bw == t->m_TEX0.TBW || (bw < t->m_TEX0.TBW && r.z <= (page_size.x * static_cast<int>(bw)) && (r.w <= page_size.y || bw <= 1));
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && rect_in_format);
if (rect_in_format && bpp_match)
{
rect = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && rect_in_format);
if (page_aligned)
{
rect = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, bw, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
}
else
{
// If it's not page aligned, grab the whole pages it covers, to be safe.
rect.x &= ~(page_size.x - 1);
rect.y &= ~(page_size.y - 1);
rect.z = (rect.z + (page_size.x - 1)) & ~(page_size.x - 1);
rect.w = (rect.w + (page_size.y - 1)) & ~(page_size.y - 1);
rect = GSLocalMemory::TranslateAlignedRectByPage(bp & ~((1 << 5) - 1), psm, bw, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
}
}
else
{
@ -523,25 +539,60 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
// Fixes Jak eyes rendering.
// Fixes Xenosaga 3 last dungeon graphic bug.
// Fixes Pause menu in The Getaway.
const GSVector2i page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs;
const bool bpp_match = GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[psm].bpp;
const bool rect_in_format = bw == t->m_TEX0.TBW || (bw < t->m_TEX0.TBW && r.z <= (page_size.x * static_cast<int>(bw)) && (r.w <= page_size.y || bw <= 1));
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && rect_in_format);
if (rect_in_format && bpp_match && page_aligned)
{
GSVector4i rect = r;
SurfaceOffset so = ComputeSurfaceOffset(bp, bw, psm, r, t);
if (!so.is_valid && t_wraps)
{
// Improves Beyond Good & Evil shadow.
const u32 bp_unwrap = bp + GSTextureCache::MAX_BP + 0x1;
so = ComputeSurfaceOffset(bp_unwrap, bw, psm, r, t);
}
if (so.is_valid)
{
rect = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, bw, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
if (rect.rempty())
continue;
if (!t->m_dirty.empty())
{
const GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, GSVector2i(rect.z, rect.w)).rintersect(rect);
if (!dirty_rect.eq(rect))
{
// Only update if the rect isn't empty
if (!dirty_rect.rempty())
t->Update(false);
}
else
continue;
}
x_offset = rect.x - r.x;
y_offset = rect.y - r.y;
dst = t;
// Offset from Target to Source in Target coords.
x_offset = so.b2a_offset.x;
y_offset = so.b2a_offset.y;
tex_in_rt = true;
tex_merge_rt = false;
found_t = true;
// Keep looking, just in case there is an exact match (Situation: Target frame drawn inside target frame, current makes a separate texture)
continue;
}
else
{
SurfaceOffset so = ComputeSurfaceOffset(bp, bw, psm, r, t);
if (!so.is_valid && t_wraps)
{
// Improves Beyond Good & Evil shadow.
const u32 bp_unwrap = bp + GSTextureCache::MAX_BP + 0x1;
so = ComputeSurfaceOffset(bp_unwrap, bw, psm, r, t);
}
if (so.is_valid)
{
dst = t;
// Offset from Target to Source in Target coords.
x_offset = so.b2a_offset.x;
y_offset = so.b2a_offset.y;
tex_in_rt = true;
tex_merge_rt = false;
found_t = true;
// Keep looking, just in case there is an exact match (Situation: Target frame drawn inside target frame, current makes a separate texture)
continue;
}
}
}
else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::MergeTargets && !tex_merge_rt)
@ -927,18 +978,28 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, con
// If the format, and location doesn't overlap
if (iter->blit.DBP >= TEX0.TBP0 && iter->blit.DBP <= rect_end && GSUtil::HasCompatibleBits(iter->blit.DPSM, TEX0.PSM))
{
GSTextureCache::SurfaceOffsetKey sok;
sok.elems[0].bp = iter->blit.DBP;
sok.elems[0].bw = iter->blit.DBW;
sok.elems[0].psm = iter->blit.DPSM;
sok.elems[0].rect = iter->rect;
sok.elems[1].bp = TEX0.TBP0;
sok.elems[1].bw = TEX0.TBW;
sok.elems[1].psm = TEX0.PSM;
sok.elems[1].rect = newrect;
// Calculate the rect offset if the BP doesn't match.
const GSVector4i targetr = (iter->blit.DBP == TEX0.TBP0) ? iter->rect : ComputeSurfaceOffset(sok).b2a_offset;
GSVector4i targetr = {};
const bool page_aligned = GSLocalMemory::IsPageAligned(iter->blit.DBP, iter->blit.DPSM, iter->rect, iter->blit.DBW == TEX0.TBW);
if (page_aligned)
{
targetr = GSLocalMemory::TranslateAlignedRectByPage(iter->blit.DBP, iter->blit.DPSM, iter->blit.DBW, iter->rect, TEX0.TBP0, TEX0.PSM, TEX0.TBW, true);
}
else
{
GSTextureCache::SurfaceOffsetKey sok;
sok.elems[0].bp = iter->blit.DBP;
sok.elems[0].bw = iter->blit.DBW;
sok.elems[0].psm = iter->blit.DPSM;
sok.elems[0].rect = iter->rect;
sok.elems[1].bp = TEX0.TBP0;
sok.elems[1].bw = TEX0.TBW;
sok.elems[1].psm = TEX0.PSM;
sok.elems[1].rect = newrect;
// Calculate the rect offset if the BP doesn't match.
targetr = (iter->blit.DBP == TEX0.TBP0) ? iter->rect : ComputeSurfaceOffset(sok).b2a_offset;
}
if (eerect.rempty())
eerect = targetr;
else
@ -1535,35 +1596,86 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
else if (check_inside_target && t->Overlaps(bp, bw, psm, rect) && GSUtil::HasSharedBits(psm, t->m_TEX0.PSM))
{
const bool bpp_match = GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[psm].bpp;
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && bw == t->m_TEX0.TBW);
// If it's compatible and page aligned, then handle it this way.
// It's quicker, and Surface Offsets can get it wrong.
// Example doing PSMT8H to C32, BP 0x1c80, TBP 0x1d80, incoming rect 0,128 -> 128,256
// Surface offsets translates it to 0, 128 -> 128, 128, not 0, 0 -> 128, 128.
if (page_aligned && bpp_match && bw == t->m_TEX0.TBW)
const GSVector2i page_size = GSLocalMemory::m_psm[psm].pgs;
bool loose_format = (bw < t->m_TEX0.TBW && r.z <= (page_size.x * static_cast<int>(bw)) && (r.w <= page_size.y/* || bw <= t->m_TEX0.TBW*/));
const bool rect_in_format = bw == t->m_TEX0.TBW || loose_format;
if (rect_in_format && bpp_match)
{
GSVector4i aligned_rect = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
AddDirtyRectTarget(t, aligned_rect, t->m_TEX0.PSM, t->m_TEX0.TBW, rgba);
}
else
{
SurfaceOffsetKey sok;
sok.elems[0].bp = bp;
sok.elems[0].bw = bw;
sok.elems[0].psm = psm;
sok.elems[0].rect = r;
sok.elems[1].bp = t->m_TEX0.TBP0;
sok.elems[1].bw = t->m_TEX0.TBW;
sok.elems[1].psm = t->m_TEX0.PSM;
sok.elems[1].rect = t->m_valid;
const SurfaceOffset so = ComputeSurfaceOffset(sok);
if (so.is_valid)
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && rect_in_format);
if (page_aligned)
{
const GSVector4i new_rect = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, bw, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW, true);
if (eewrite)
t->m_age = 0;
AddDirtyRectTarget(t, so.b2a_offset, t->m_TEX0.PSM, t->m_TEX0.TBW, rgba);
AddDirtyRectTarget(t, new_rect, t->m_TEX0.PSM, t->m_TEX0.TBW, rgba);
}
else
{
// If it's not page aligned, grab the whole pages it covers, to be safe.
GSVector4i new_rect = r;
new_rect.x &= ~(page_size.x - 1);
new_rect.y &= ~(page_size.y - 1);
new_rect.z = (r.z + (page_size.x - 1)) & ~(page_size.x - 1);
new_rect.w = (r.w + (page_size.y - 1)) & ~(page_size.y - 1);
new_rect = GSLocalMemory::TranslateAlignedRectByPage(bp & ~((1 << 5) - 1), psm, bw, new_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW, true);
if (eewrite)
t->m_age = 0;
AddDirtyRectTarget(t, new_rect, t->m_TEX0.PSM, t->m_TEX0.TBW, rgba);
}
}
else
{
if (GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp >= 16 && GSLocalMemory::m_psm[psm].bpp <= 8)
{
// could be overwriting a double buffer, so if it's the second half of it, just reduce the size down to half.
if (((t->m_end_block - t->m_TEX0.TBP0) >> 1) < (bp - t->m_TEX0.TBP0))
{
GSVector4i new_valid = t->m_valid;
new_valid.w = new_valid.w / 2;
t->ResizeValidity(new_valid);
}
else
{
i = list.erase(j);
GL_CACHE("TC: Tex in RT Remove Target(%s) %d (0x%x) TPSM %x PSM %x bp 0x%x", to_string(type),
t->m_texture ? t->m_texture->GetID() : 0,
t->m_TEX0.TBP0,
t->m_TEX0.PSM,
psm,
bp);
delete t;
continue;
}
}
else
{
SurfaceOffsetKey sok;
sok.elems[0].bp = bp;
sok.elems[0].bw = bw;
sok.elems[0].psm = psm;
sok.elems[0].rect = r;
sok.elems[1].bp = t->m_TEX0.TBP0;
sok.elems[1].bw = t->m_TEX0.TBW;
sok.elems[1].psm = t->m_TEX0.PSM;
sok.elems[1].rect = t->m_valid;
const SurfaceOffset so = ComputeSurfaceOffset(sok);
if (so.is_valid)
{
if (eewrite)
t->m_age = 0;
AddDirtyRectTarget(t, so.b2a_offset, t->m_TEX0.PSM, t->m_TEX0.TBW, rgba);
}
}
}
}
@ -1592,10 +1704,11 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
r.z,
r.w);
// No depth handling please.
if (psm == PSM_PSMZ32 || psm == PSM_PSMZ24 || psm == PSM_PSMZ16 || psm == PSM_PSMZ16S)
const bool read_is_depth = (psm & 0x30) == 0x30;
// Could be reading Z24/32 back as CT32 (Gundam Battle Assault 3)
if (GSLocalMemory::m_psm[psm].bpp >= 16)
{
GL_INS("ERROR: InvalidateLocalMem depth format isn't supported (%d,%d to %d,%d)", r.x, r.y, r.z, r.w);
if (GSConfig.HWDownloadMode != GSHardwareDownloadMode::Enabled)
{
DevCon.Error("Skipping depth readback of %ux%u @ %u,%u", r.width(), r.height(), r.left, r.top);
@ -1618,18 +1731,55 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
const bool expecting_this_tex = bpp_match && (((read_start & ~page_mask) == t->m_TEX0.TBP0) || (bp >= t->m_TEX0.TBP0 && ((read_end + page_mask) & ~page_mask) <= ((t->m_end_block + page_mask) & ~page_mask)));
if (!expecting_this_tex)
continue;
z_found = true;
if (t->m_drawn_since_read.eq(GSVector4i::zero()))
return;
z_found = true;
t->readbacks_since_draw++;
const bool format_match = (bp == t->m_TEX0.TBP0 && bw == t->m_TEX0.TBW && bpp_match);
const bool tex_is_depth = (t->m_TEX0.PSM & 0x30) == 0x30;
GSVector2i page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs;
const bool rect_in_format = bw == t->m_TEX0.TBW || (bw < t->m_TEX0.TBW && r.z <= (page_size.x * static_cast<int>(bw)) && (r.w <= page_size.y || bw <= 1));
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && rect_in_format);
// Calculate the rect offset if the BP doesn't match.
const GSVector4i targetr = (format_match) ? r.rintersect(t->m_valid) : ComputeSurfaceOffset(bp, bw, psm, r, t).b2a_offset;
GSVector4i targetr = {};
if (t->readbacks_since_draw > 1)
{
targetr = t->m_drawn_since_read;
}
else if (rect_in_format && bpp_match)
{
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && rect_in_format);
if (page_aligned && (tex_is_depth == read_is_depth))
{
targetr = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, bw, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW, true);
}
else
{
// If it's not page aligned, grab the whole pages it covers, to be safe.
GSVector4i rect = r;
rect.x &= ~(page_size.x - 1);
rect.y &= ~(page_size.y - 1);
rect.z = (rect.z + (page_size.x - 1)) & ~(page_size.x - 1);
rect.w = (rect.w + (page_size.y - 1)) & ~(page_size.y - 1);
targetr = GSLocalMemory::TranslateAlignedRectByPage(bp & ~((1 << 5) - 1), psm, bw, rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW, true);
}
}
else
{
targetr = t->m_drawn_since_read;
}
const GSVector4i draw_rect = (t->readbacks_since_draw > 1) ? t->m_drawn_since_read : targetr.rintersect(t->m_drawn_since_read);
if (t->m_drawn_since_read.eq(GSVector4i::zero()))
{
if (draw_rect.rintersect(t->m_valid).eq(draw_rect))
return;
else
continue;
}
Read(t, draw_rect);
if (draw_rect.rintersect(t->m_drawn_since_read).eq(t->m_drawn_since_read))
t->m_drawn_since_read = GSVector4i::zero();
@ -1651,8 +1801,6 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
// Fatal Frame series
auto& rts = m_dst[RenderTarget];
const bool read_is_depth = (psm & 0x30) == 0x30;
// Check exact match first
for (auto it = rts.rbegin(); it != rts.rend(); it++) // Iterate targets from LRU to MRU.
{
@ -1668,54 +1816,50 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
if (!expecting_this_tex)
continue;
if (t->m_drawn_since_read.eq(GSVector4i::zero()))
return;
t->readbacks_since_draw++;
const bool tex_is_depth = (t->m_TEX0.PSM & 0x30) == 0x30;
GSVector2i page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs;
const bool rect_in_format = bw == t->m_TEX0.TBW || (bw < t->m_TEX0.TBW && r.z <= (page_size.x * static_cast<int>(bw)) && (r.w <= page_size.y || bw <= 1));
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && rect_in_format);
// Calculate the rect offset if the BP doesn't match.
GSVector4i targetr = {};
const bool tex_is_depth = (t->m_TEX0.PSM & 0x30) == 0x30;
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && bw == t->m_TEX0.TBW);
if (t->readbacks_since_draw > 1 || (!page_aligned && (tex_is_depth != read_is_depth)))
if (t->readbacks_since_draw > 1)
{
targetr = t->m_drawn_since_read;
else if (page_aligned && bw == t->m_TEX0.TBW)
{
targetr = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
}
else
else if (rect_in_format && bpp_match)
{
const bool format_match = (bp == t->m_TEX0.TBP0 && bw == t->m_TEX0.TBW && bpp_match);
if (!format_match)
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, bpp_match && rect_in_format);
if (page_aligned && (tex_is_depth == read_is_depth))
{
SurfaceOffsetKey sok;
sok.elems[0].bp = bp;
sok.elems[0].bw = bw;
sok.elems[0].psm = psm;
sok.elems[0].rect = r;
sok.elems[1].bp = t->m_TEX0.TBP0;
sok.elems[1].bw = t->m_TEX0.TBW;
sok.elems[1].psm = t->m_TEX0.PSM;
sok.elems[1].rect = t->m_valid;
SurfaceOffset so = ComputeSurfaceOffset(sok);
targetr = so.b2a_offset;
targetr = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, bw, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW, true);
}
else
{
targetr = r;
// If it's not page aligned, grab the whole pages it covers, to be safe.
GSVector4i rect = r;
rect.x &= ~(page_size.x - 1);
rect.y &= ~(page_size.y - 1);
rect.z = (rect.z + (page_size.x - 1)) & ~(page_size.x - 1);
rect.w = (rect.w + (page_size.y - 1)) & ~(page_size.y - 1);
targetr = GSLocalMemory::TranslateAlignedRectByPage(bp & ~((1 << 5) - 1), psm, bw, rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW, true);
}
}
else
{
targetr = t->m_drawn_since_read;
}
targetr = targetr.rintersect(t->m_drawn_since_read);
if (t->m_drawn_since_read.rempty())
if (t->m_drawn_since_read.eq(GSVector4i::zero()))
{
// we found the exact one but it's already been read back.
if (targetr.rintersect(t->m_valid).eq(targetr))
return;
else
continue;
}
if (!targetr.rempty())
@ -1766,39 +1910,51 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
if (t->m_drawn_since_read.eq(GSVector4i::zero()))
return;
t->readbacks_since_draw++;
// Calculate the rect offset if the BP doesn't match.
GSVector4i targetr = {};
const bool tex_is_depth = (t->m_TEX0.PSM & 0x30) == 0x30;
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, !bpp_mismatch && bw == t->m_TEX0.TBW);
GSVector2i page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs;
const bool rect_in_format = bw == t->m_TEX0.TBW || (bw < t->m_TEX0.TBW && r.z <= (page_size.x * static_cast<int>(bw)) && (r.w <= page_size.y || bw <= 1));
if (t->readbacks_since_draw > 1 || !page_aligned || (tex_is_depth != read_is_depth))
targetr = t->m_drawn_since_read;
else
if (t->readbacks_since_draw > 1)
{
if (page_aligned && bw == t->m_TEX0.TBW)
targetr = t->m_drawn_since_read;
}
else if (rect_in_format && !bpp_mismatch)
{
const bool page_aligned = GSLocalMemory::IsPageAligned(bp, psm, r, !bpp_mismatch && rect_in_format);
if (page_aligned && (tex_is_depth == read_is_depth))
{
targetr = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
targetr = GSLocalMemory::TranslateAlignedRectByPage(bp, psm, bw, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW, true);
}
else
{
const bool format_match = (bp == t->m_TEX0.TBP0 && bw == t->m_TEX0.TBW && !bpp_mismatch);
SurfaceOffsetKey sok;
sok.elems[0].bp = bp;
sok.elems[0].bw = bw;
sok.elems[0].psm = psm;
sok.elems[0].rect = r;
sok.elems[1].bp = t->m_TEX0.TBP0;
sok.elems[1].bw = t->m_TEX0.TBW;
sok.elems[1].psm = t->m_TEX0.PSM;
sok.elems[1].rect = t->m_valid;
SurfaceOffset so = ComputeSurfaceOffset(sok);
targetr = GSVector4i((format_match) ? r.rintersect(t->m_drawn_since_read) : so.b2a_offset);
// If it's not page aligned, grab the whole pages it covers, to be safe.
GSVector4i rect = r;
rect.x &= ~(page_size.x - 1);
rect.y &= ~(page_size.y - 1);
rect.z = (rect.z + (page_size.x - 1)) & ~(page_size.x - 1);
rect.w = (rect.w + (page_size.y - 1)) & ~(page_size.y - 1);
targetr = GSLocalMemory::TranslateAlignedRectByPage(bp & ~((1 << 5) - 1), psm, bw, rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW, true);
}
}
else
{
targetr = t->m_drawn_since_read;
}
if (t->m_drawn_since_read.eq(GSVector4i::zero()))
{
if (targetr.rintersect(t->m_valid).eq(targetr))
return;
else
continue;
}
targetr.rintersect(t->m_drawn_since_read);
if (!targetr.rempty())
{
Read(t, targetr);
@ -3254,7 +3410,7 @@ void GSTextureCache::Read(Target* t, const GSVector4i& r)
const u32 write_mask = t->m_valid_bits & psm.fmsk;
if (psm.bpp > 16 && write_mask == 0)
{
Console.Warning("Not reading back target %x PSM %s due to no write mask", TEX0.TBP0, psm_str(TEX0.PSM));
DbgCon.Warning("Not reading back target %x PSM %s due to no write mask", TEX0.TBP0, psm_str(TEX0.PSM));
return;
}