diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index f71390beb0..a7c6396435 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -162,6 +162,13 @@ void GSRendererHW::PurgeTextureCache() m_tc->RemoveAll(); } +bool GSRendererHW::IsPossibleTextureShuffle(GSTextureCache::Source* src) const +{ + return (PRIM->TME && m_vt.m_primclass == GS_SPRITE_CLASS && + src->m_32_bits_fmt && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 16 && + GSLocalMemory::m_psm[m_context->FRAME.PSM].bpp == 16); +} + void GSRendererHW::SetGameCRC(u32 crc, int options) { GSRenderer::SetGameCRC(crc, options); @@ -1711,6 +1718,11 @@ void GSRendererHW::Draw() // + // Temporary source *must* be invalidated before normal, because otherwise it'll be double freed. + m_tc->InvalidateTemporarySource(); + + // + if ((fm & fm_mask) != fm_mask && rt) { //rt->m_valid = rt->m_valid.runion(r); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index f2f037b00b..c86449e18e 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -186,4 +186,7 @@ public: // Called by the texture cache to know if current texture is useful virtual bool IsDummyTexture() const { return false; } + + // Called by the texture cache when optimizing the copy range for sources + bool IsPossibleTextureShuffle(GSTextureCache::Source* src) const; }; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 723c5b0d52..e1784b6be7 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -391,7 +391,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con GL_CACHE("TC: src miss (0x%x, 0x%x, %s)", TEX0.TBP0, psm_s.pal > 0 ? TEX0.CBP : 0, psm_str(TEX0.PSM)); } #endif - src = CreateSource(TEX0, TEXA, dst, half_right, x_offset, y_offset, lod); + src = CreateSource(TEX0, TEXA, dst, half_right, x_offset, y_offset, lod, &r); new_source = true; } else @@ -1165,7 +1165,7 @@ void GSTextureCache::IncAge() } //Fixme: Several issues in here. Not handling depth stencil, pitch conversion doesnt work. -GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* dst, bool half_right, int x_offset, int y_offset, const GSVector2i* lod) +GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* dst, bool half_right, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range) { const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM]; Source* src = new Source(m_renderer, TEX0, TEXA, false); @@ -1340,8 +1340,10 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con GSVector2 scale = is_8bits ? GSVector2(1, 1) : dst->m_texture->GetScale(); - GSVector4i sRect(0, 0, w, h); const bool use_texture = shader == ShaderConvert::COPY; + GSVector4i sRect(0, 0, w, h); + int destX = 0; + int destY = 0; if (half_right) { @@ -1361,6 +1363,27 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con DevCon.Error("Invalid half-right copy with width %d from %dx%d texture", half_width * 2, w, h); } } + else if (src_range && dst->m_TEX0.TBW == TEX0.TBW && !is_8bits) + { + // optimization for TBP == FRAME + const GSDrawingContext* const context = m_renderer->m_context; + if (context->FRAME.Block() == TEX0.TBP0 || context->ZBUF.Block() == TEX0.TBP0) + { + // if it looks like a texture shuffle, we might read up to +/- 8 pixels on either side. + GSVector4 adjusted_src_range(*src_range); + if (static_cast(m_renderer)->IsPossibleTextureShuffle(src)) + adjusted_src_range += GSVector4(-8.0f, 0.0f, 8.0f, 0.0f); + + // don't forget to scale the copy range + adjusted_src_range = adjusted_src_range * GSVector4(scale).xyxy(); + sRect = sRect.rintersect(GSVector4i(adjusted_src_range)); + destX = sRect.x; + destY = sRect.y; + + // clean up immediately afterwards + m_temporary_source = src; + } + } // Don't be fooled by the name. 'dst' is the old target (hence the input) // 'src' is the new texture cache entry (hence the output) @@ -1380,7 +1403,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con if (use_texture) { - g_gs_device->CopyRect(sTex, dTex, sRect, 0, 0); + g_gs_device->CopyRect(sTex, dTex, sRect, destX, destY); } else { @@ -1388,7 +1411,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con sRectF.z /= sTex->GetWidth(); sRectF.w /= sTex->GetHeight(); - g_gs_device->StretchRect(sTex, sRectF, dTex, GSVector4(0, 0, w, h), shader, false); + g_gs_device->StretchRect(sTex, sRectF, dTex, GSVector4(destX, destY, w, h), shader, false); } if (src->m_texture) @@ -2439,6 +2462,15 @@ GSTextureCache::SurfaceOffset GSTextureCache::ComputeSurfaceOffset(const Surface return so; } +void GSTextureCache::InvalidateTemporarySource() +{ + if (!m_temporary_source) + return; + + m_src.RemoveAt(m_temporary_source); + m_temporary_source = nullptr; +} + void GSTextureCache::InjectHashCacheTexture(const HashCacheKey& key, GSTexture* tex) { auto it = m_hash_cache.find(key); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index a59ed81485..4639df7e5c 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -285,8 +285,9 @@ protected: static u8* m_temp; constexpr static size_t S_SURFACE_OFFSET_CACHE_MAX_SIZE = std::numeric_limits::max(); std::unordered_map m_surface_offset_cache; + Source* m_temporary_source = nullptr; // invalidated after the draw - Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t = NULL, bool half_right = false, int x_offset = 0, int y_offset = 0, const GSVector2i* lod = nullptr); + Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t = NULL, bool half_right = false, int x_offset = 0, int y_offset = 0, const GSVector2i* lod = nullptr, const GSVector4i* src_range = nullptr); Target* CreateTarget(const GIFRegTEX0& TEX0, int w, int h, int type, const bool clear); HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod); @@ -333,6 +334,9 @@ public: SurfaceOffset ComputeSurfaceOffset(const uint32_t bp, const uint32_t bw, const uint32_t psm, const GSVector4i& r, const Target* t); SurfaceOffset ComputeSurfaceOffset(const SurfaceOffsetKey& sok); + /// Invalidates a temporary source, a partial copy only created from the current RT/DS for the current draw. + void InvalidateTemporarySource(); + /// Injects a texture into the hash cache, by using GSTexture::Swap(), transitively applying to all sources. Ownership of tex is transferred. void InjectHashCacheTexture(const HashCacheKey& key, GSTexture* tex); };