diff --git a/pcsx2/GS/GSLocalMemory.cpp b/pcsx2/GS/GSLocalMemory.cpp index 9037d4b997..92330cd434 100644 --- a/pcsx2/GS/GSLocalMemory.cpp +++ b/pcsx2/GS/GSLocalMemory.cpp @@ -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(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; } diff --git a/pcsx2/GS/GSLocalMemory.h b/pcsx2/GS/GSLocalMemory.h index 3304113618..71f0df5c86 100644 --- a/pcsx2/GS/GSLocalMemory.h +++ b/pcsx2/GS/GSLocalMemory.h @@ -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) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index fc43f030ce..7afaafafde 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -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"); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 90f6b3fd7a..60496dc873 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -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(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(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(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(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(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(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; }