diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index c9e08a2333..dac844172d 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -3996,8 +3996,14 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL // If it's the start of the texture and our little adjustment is all that pushed it over, clamp it to 0. // This stops the border check failing when using repeat but needed less than the full texture // since this was making it take the full texture even though it wasn't needed. - if (!clamp_to_tsize && ((m_vt.m_min.t.floor() == GSVector4::zero()).mask() & 0x3) == 0x3) - st = st.max(GSVector4::zero()); + if (!clamp_to_tsize) + { + const u32 mask = (m_vt.m_min.t.floor() == GSVector4::zero()).mask(); + if (mask & 1) // X == 0 + st.x = st.max(GSVector4::zero()).x; + if (mask & 2) // Y == 0 + st.y = st.max(GSVector4::zero()).y; + } } // draw will get scissored, adjust UVs to suit diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 0d3b5e7430..dc073b00b0 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -3686,7 +3686,7 @@ void GSRendererHW::Draw() m_vt.m_min.t *= 0.5f; m_vt.m_max.t *= 0.5f; - tmm = GetTextureMinMax(MIP_TEX0, MIP_CLAMP, m_vt.IsLinear(), false); + tmm = GetTextureMinMax(MIP_TEX0, MIP_CLAMP, m_vt.IsLinear(), true); src->UpdateLayer(MIP_TEX0, tmm.coverage, layer - m_lod.x); } @@ -3881,7 +3881,7 @@ void GSRendererHW::Draw() const bool new_rect = rt->m_valid.rempty(); const bool new_height = new_h > rt->GetUnscaledHeight(); const int old_height = rt->m_texture->GetHeight(); - + bool merge_targets = false; pxAssert(rt->GetScale() == target_scale); if (rt->GetUnscaledWidth() != new_w || rt->GetUnscaledHeight() != new_h) GL_INS("HW: Resize RT from %dx%d to %dx%d", rt->GetUnscaledWidth(), rt->GetUnscaledHeight(), new_w, new_h); @@ -3904,7 +3904,10 @@ void GSRendererHW::Draw() g_texture_cache->AddDirtyRectTarget(rt, height_dirty_rect, rt->m_TEX0.PSM, rt->m_TEX0.TBW, mask); } }*/ - + + if ((new_w > rt->m_unscaled_size.x || new_h > rt->m_unscaled_size.y) && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) + merge_targets = true; + rt->ResizeTexture(new_w, new_h); if (!m_texture_shuffle && !m_channel_shuffle) @@ -3935,6 +3938,8 @@ void GSRendererHW::Draw() rt->UpdateValidity(update_rect, !frame_masked && (rt_update || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); rt->UpdateDrawn(update_rect, !frame_masked && (rt_update || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); + if (merge_targets) + g_texture_cache->CombineAlignedInsideTargets(rt, src); // Probably changing to double buffering, so invalidate any old target that was next to it. // This resolves an issue where the PCRTC will find the old target in FMV's causing flashing. // Grandia Xtreme, Onimusha Warlord. @@ -6296,56 +6301,58 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c // Detect framebuffer read that will need special handling const GSTextureCache::Target* src_target = nullptr; - - if (rt && m_conf.tex == m_conf.rt && !(m_channel_shuffle && tex && (tex_diff != frame_diff || target_region))) + if (!m_downscale_source || !tex->m_from_target) { - // Can we read the framebuffer directly? (i.e. sample location matches up). - if (CanUseTexIsFB(rt, tex, tmm)) + if (rt && m_conf.tex == m_conf.rt && !(m_channel_shuffle && tex && (tex_diff != frame_diff || target_region))) { - m_conf.tex = nullptr; - m_conf.ps.tex_is_fb = true; - if (m_prim_overlap == PRIM_OVERLAP_NO || !g_gs_device->Features().texture_barrier) - m_conf.require_one_barrier = true; - else - m_conf.require_full_barrier = true; + // Can we read the framebuffer directly? (i.e. sample location matches up). + if (CanUseTexIsFB(rt, tex, tmm)) + { + m_conf.tex = nullptr; + m_conf.ps.tex_is_fb = true; + if (m_prim_overlap == PRIM_OVERLAP_NO || !g_gs_device->Features().texture_barrier) + m_conf.require_one_barrier = true; + else + m_conf.require_full_barrier = true; - unscaled_size = rt->GetUnscaledSize(); - scale = rt->GetScale(); + unscaled_size = rt->GetUnscaledSize(); + scale = rt->GetScale(); + return; + } + + GL_CACHE("HW: Source is render target, taking copy."); + src_target = rt; + } + // Be careful of single page channel shuffles where depth is the source but it's not going to the same place, we can't read this directly. + else if (ds && m_conf.tex == m_conf.ds && (!m_channel_shuffle || static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) == static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0))) + { + // GL, Vulkan (in General layout), not DirectX! + const bool can_read_current_depth_buffer = g_gs_device->Features().test_and_sample_depth; + + // If this is our current Z buffer, we might not be able to read it directly if it's being written to. + // Rather than leaving the backend to do it, we'll check it here. + if (can_read_current_depth_buffer && (m_cached_ctx.ZBUF.ZMSK || m_cached_ctx.TEST.ZTST == ZTST_NEVER)) + { + // Safe to read! + GL_CACHE("HW: Source is depth buffer, not writing, safe to read."); + unscaled_size = ds->GetUnscaledSize(); + scale = ds->GetScale(); + return; + } + + // Can't safely read the depth buffer, so we need to take a copy of it. + GL_CACHE("HW: Source is depth buffer, unsafe to read, taking copy."); + src_target = ds; + } + else if (m_channel_shuffle && tex->m_from_target && tex_diff != frame_diff) + { + src_target = tex->m_from_target; + } + else + { + // No match. return; } - - GL_CACHE("HW: Source is render target, taking copy."); - src_target = rt; - } - // Be careful of single page channel shuffles where depth is the source but it's not going to the same place, we can't read this directly. - else if (ds && m_conf.tex == m_conf.ds && (!m_channel_shuffle || static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) == static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0))) - { - // GL, Vulkan (in General layout), not DirectX! - const bool can_read_current_depth_buffer = g_gs_device->Features().test_and_sample_depth; - - // If this is our current Z buffer, we might not be able to read it directly if it's being written to. - // Rather than leaving the backend to do it, we'll check it here. - if (can_read_current_depth_buffer && (m_cached_ctx.ZBUF.ZMSK || m_cached_ctx.TEST.ZTST == ZTST_NEVER)) - { - // Safe to read! - GL_CACHE("HW: Source is depth buffer, not writing, safe to read."); - unscaled_size = ds->GetUnscaledSize(); - scale = ds->GetScale(); - return; - } - - // Can't safely read the depth buffer, so we need to take a copy of it. - GL_CACHE("HW: Source is depth buffer, unsafe to read, taking copy."); - src_target = ds; - } - else if (m_channel_shuffle && tex->m_from_target && tex_diff != frame_diff) - { - src_target = tex->m_from_target; - } - else if (!m_downscale_source || !tex->m_from_target) - { - // No match. - return; } else src_target = tex->m_from_target; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index ac3a6a85d3..81cb33b058 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -240,7 +240,7 @@ bool GSTextureCache::CanTranslate(u32 bp, u32 bw, u32 spsm, GSVector4i r, u32 db const bool page_aligned_rect = masked_rect.xyxy().eq(r.xyxy()); const bool width_match = ((bw * 64) / src_page_size.x) == ((dbw * 64) / dst_page_size.x); const bool sequential_pages = page_aligned_rect && r.x == 0 && r.z == src_pixel_width; - const bool single_row = (((bw * 64) / src_page_size.x) <= ((dbw * 64) / dst_page_size.x)) && r.z <= src_pixel_width && r.w <= src_page_size.y; + const bool single_row = (((bw * 64) / src_page_size.x) <= ((dbw * 64) / dst_page_size.x)) && r.width() <= src_pixel_width && r.height() <= src_page_size.y; const bool single_page_aligned = page_aligned_rect && r.z <= src_page_size.x && r.w <= src_page_size.y; if (block_layout_match) { @@ -1426,10 +1426,17 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const const u32 channels = t->m_dirty.GetDirtyChannels() & channel_mask; // If the source is reading the rt, make sure it's big enough. - if (!possible_shuffle && t && GSUtil::HasCompatibleBits(psm, t->m_TEX0.PSM) && real_fmt_match) + if (!possible_shuffle && t && GSUtil::HasCompatibleBits(psm, t->m_TEX0.PSM)&& real_fmt_match) { + // Try to clamp the size of the target when using repeat, we don't want it getting too huge. + GSVector4i resize_rect = new_rect; + if (CLAMP.WMS == 0 || CLAMP.WMS == 3) + resize_rect.z = std::min(resize_rect.z, static_cast(t->m_TEX0.TBW) * 64); + if ((CLAMP.WMT == 0 || CLAMP.WMT == 3) && resize_rect.w > (t->m_valid.w * 2)) + resize_rect.w = std::min(resize_rect.w, t->m_valid.w * 2); + if (t->Overlaps(bp, bw, psm, new_rect)) - ResizeTarget(t, new_rect, bp, psm, bw); + ResizeTarget(t, resize_rect, bp, psm, bw); } // If not all channels are clean/dirty or only part of the rect is dirty, we need to update the target. if (((channels & channel_mask) != channel_mask || partial)) @@ -1462,8 +1469,16 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (block_boundary_rect.w > t->m_valid.w) AddDirtyRectTarget(t, GSVector4i(t->m_valid.x, t->m_valid.w, std::max(block_boundary_rect.z, t->m_valid.z), block_boundary_rect.w), t->m_TEX0.PSM, t->m_TEX0.TBW, rgba_mask); } + + // Try to clamp the size of the target when using repeat, we don't want it getting too huge. + GSVector4i resize_rect = req_rect; + if (CLAMP.WMS == 0 || CLAMP.WMS == 3) + resize_rect.z = std::min(resize_rect.z, static_cast(t->m_TEX0.TBW) * 64); + if ((CLAMP.WMT == 0 || CLAMP.WMT == 3) && resize_rect.w > (t->m_valid.w * 2)) + resize_rect.w = std::min(resize_rect.w, t->m_valid.w * 2); + // Resize including the extra pixel for bilinear. - ResizeTarget(t, req_rect, bp, psm, bw); + ResizeTarget(t, resize_rect, bp, psm, bw); } } } @@ -1633,6 +1648,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const { u32 rt_tbw = std::max(1U, t->m_TEX0.TBW); u32 horz_page_offset = ((bp - t->m_TEX0.TBP0) >> 5) % rt_tbw; + + if (GSLocalMemory::m_psm[psm].bpp == GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && bw != rt_tbw && block_boundary_rect.height() > GSLocalMemory::m_psm[psm].pgs.y) + continue; + if (GSLocalMemory::m_psm[color_psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && bw != 1 && ((t->m_TEX0.TBW < (horz_page_offset + ((block_boundary_rect.z + GSLocalMemory::m_psm[psm].pgs.x - 1) / GSLocalMemory::m_psm[psm].pgs.x)) || (t->m_TEX0.TBW != bw && block_boundary_rect.w > GSLocalMemory::m_psm[psm].pgs.y)))) @@ -2089,14 +2108,19 @@ void GSTextureCache::CombineAlignedInsideTargets(Target* target, GSTextureCache: if ((page_offset + page_width) <= target->m_TEX0.TBW) { - const u32 vertical_offset = (((t->m_TEX0.TBP0 - target->m_TEX0.TBP0) >> 5) / std::max(1U, t->m_TEX0.TBW)) * t_psm.pgs.y; - const u32 horizontal_offset = page_offset * t_psm.pgs.x; - const GSVector4i target_drect_unscaled = t->m_valid + GSVector4i(horizontal_offset, vertical_offset).xyxy(); - const GSVector4 target_drect = GSVector4(target_drect_unscaled) * target->m_scale; + if (t->m_last_draw > target->m_last_draw || t->m_valid.rintersect(target->m_valid).rempty()) + { + t->Update(); + const u32 vertical_offset = (((t->m_TEX0.TBP0 - target->m_TEX0.TBP0) >> 5) / std::max(1U, t->m_TEX0.TBW)) * t_psm.pgs.y; + const u32 horizontal_offset = page_offset * t_psm.pgs.x; + const GSVector4i target_drect_unscaled = t->m_valid + GSVector4i(horizontal_offset, vertical_offset).xyxy(); + const GSVector4 target_drect = GSVector4(target_drect_unscaled) * target->m_scale; + const bool valid_color = t->m_valid_rgb; + const bool valid_alpha = (t->m_valid_alpha_high | t->m_valid_alpha_low) && (GSUtil::GetChannelMask(t->m_TEX0.PSM) & 0x8); + g_gs_device->StretchRect(t->m_texture, GSVector4(0, 0, 1, 1), target->m_texture, target_drect, valid_color, valid_color, valid_color, valid_alpha, (target->m_type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY); - g_gs_device->StretchRect(t->m_texture, GSVector4(0, 0, 1, 1), target->m_texture, target_drect, (target->m_type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, t->m_scale < target->m_scale); - - target->UpdateValidity(target_drect_unscaled); + target->UpdateValidity(target_drect_unscaled); + } if (src && src->m_from_target == t) { @@ -2286,13 +2310,19 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe InvalidateSourcesFromTarget(t); i = list.erase(i); delete t; - + continue; } - else if (t->m_dirty.empty() || (t->m_TEX0.TBP0 <= bp && t->m_last_draw >= (GSState::s_n - 1) && - t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size).rintersect(GSVector4i(0, 0, 0, 0).max_i32(TranslateAlignedRectByPage(t, TEX0.TBP0, TEX0.PSM, TEX0.TBW, min_rect))).rempty())) + GSVector4i lookup_rect = min_rect; + + if (is_shuffle) + lookup_rect = lookup_rect & GSVector4i(~8); + + const GSVector4i translated_rect = GSVector4i(0, 0, 0, 0).max_i32(TranslateAlignedRectByPage(t, TEX0.TBP0, TEX0.PSM, TEX0.TBW, lookup_rect)); + + if ((translated_rect.w <= t->m_valid.w) || widthpage_offset == 0 || (GSState::s_n - 1) == t->m_last_draw) { - if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63)/ 64) > 1) + if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63) / 64) > 1) { // Beyond Good and Evil does this awful thing where it puts one framebuffer at 0xf00, with the first row of pages blanked out, and the whole thing goes down to 0x2080 // which is a problem, because it then puts the Z buffer at 0x1fc0, then offsets THAT by 1 row of pages, so it starts at, you guessed it, 2080. @@ -3036,6 +3066,8 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe else dst->m_last_draw += 1; // If we preload and it needs to decorrect and we couldn't catch it early, we need to make sure it decorrects the data. + CombineAlignedInsideTargets(dst, src); + pxAssert(dst && dst->m_texture && dst->m_scale == scale); return dst; } @@ -3859,8 +3891,9 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr continue; } - // If not fully contained, just dirty the area. - if (start_bp != t->m_TEX0.TBP0 && (t->m_TEX0.TBP0 < start_bp || t->UnwrappedEndBlock() > end_bp)) + const u32 offset = (std::abs(static_cast(start_bp - t->m_TEX0.TBP0)) >> 5) % std::max(1U, t->m_TEX0.TBW); + // If not fully contained but they are aligned and or clean, just dirty the area. + if (start_bp != t->m_TEX0.TBP0 && (t->m_TEX0.TBP0 < start_bp || t->UnwrappedEndBlock() > end_bp) && (offset == 0 || t->m_dirty.size() == 0)) { if (write_bw == t->m_TEX0.TBW && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[write_psm].bpp) {