diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 1b318e83b1..6323ede063 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -217,7 +217,6 @@ protected: bool IsOpaque(); bool IsMipMapDraw(); bool IsMipMapActive(); - GIFRegTEX0 GetTex0Layer(u32 lod); public: GIFPath m_path[4]; @@ -303,4 +302,5 @@ public: void SetFrameSkip(int skip); PRIM_OVERLAP PrimitiveOverlap(); + GIFRegTEX0 GetTex0Layer(u32 lod); }; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index f4a4ac855f..b694d9d6d6 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1331,6 +1331,7 @@ void GSRendererHW::Draw() if (PRIM->TME) { GIFRegCLAMP MIP_CLAMP = context->CLAMP; + GSVector2i hash_lod_range(0, 0); m_lod = GSVector2i(0, 0); // Code from the SW renderer @@ -1393,6 +1394,10 @@ void GSRendererHW::Draw() TEX0 = GetTex0Layer(m_lod.x); + // upload the full chain (with offset) for the hash cache, in case some other texture uses more levels + // for basic mipmapping, we can get away with just doing the base image, since all the mips get generated anyway. + hash_lod_range = GSVector2i(m_lod.x, (m_hw_mipmap == HWMipmapLevel::Full) ? mxl : m_lod.x); + MIP_CLAMP.MINU >>= m_lod.x; MIP_CLAMP.MINV >>= m_lod.x; MIP_CLAMP.MAXU >>= m_lod.x; @@ -1416,8 +1421,8 @@ void GSRendererHW::Draw() TextureMinMaxResult tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_vt.IsLinear()); m_src = tex_psm.depth ? m_tc->LookupDepthSource(TEX0, env.TEXA, tmm.coverage) : - m_tc->LookupSource(TEX0, env.TEXA, tmm.coverage, m_hw_mipmap >= HWMipmapLevel::Basic || - GSConfig.UserHacks_TriFilter == TriFiltering::Forced); + m_tc->LookupSource(TEX0, env.TEXA, tmm.coverage, (m_hw_mipmap >= HWMipmapLevel::Basic || + GSConfig.UserHacks_TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr); int tw = 1 << TEX0.TW; int th = 1 << TEX0.TH; @@ -1471,7 +1476,7 @@ void GSRendererHW::Draw() } // Round 2 - if (IsMipMapActive() && m_hw_mipmap == HWMipmapLevel::Full && !tex_psm.depth) + if (IsMipMapActive() && m_hw_mipmap == HWMipmapLevel::Full && !tex_psm.depth && !m_src->m_from_hash_cache) { // Upload remaining texture layers const GSVector4 tmin = m_vt.m_min.t; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 87fcbc4c86..afe38d7818 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -17,6 +17,7 @@ #include "GSTextureCache.h" #include "GS/Renderers/Common/GSFunctionMap.h" +#include "GS/Renderers/Common/GSRenderer.h" #include "GS/GSState.h" class GSRendererHW : public GSRenderer diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 655e2220a5..b399a98949 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -187,7 +187,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0 else if (m_renderer->m_game.title == CRC::SVCChaos) { // SVCChaos black screen on main menu, regardless of depth enabled or disabled. - return LookupSource(TEX0, TEXA, r, false); + return LookupSource(TEX0, TEXA, r, nullptr); } else { @@ -210,7 +210,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0 return src; } -GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool mipmap) +GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, const GSVector2i* lod) { GL_CACHE("TC: Lookup Source <%d,%d => %d,%d> (0x%x, %s, BW: %u)", r.x, r.y, r.z, r.w, TEX0.TBP0, psm_str(TEX0.PSM), TEX0.TBW); @@ -413,7 +413,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, mipmap); + src = CreateSource(TEX0, TEXA, dst, half_right, x_offset, y_offset, lod); new_source = true; } else @@ -1186,7 +1186,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, bool mipmap) +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 GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM]; Source* src = new Source(m_renderer, TEX0, TEXA, false); @@ -1451,19 +1451,31 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con else { // try the hash cache - if (!mipmap && CanCacheTextureSize(TEX0.TW, TEX0.TH)) + if (CanCacheTextureSize(TEX0.TW, TEX0.TH)) { const bool paltex = (GSConfig.GPUPaletteConversion && psm.pal > 0); const u32* clut = (!paltex && psm.pal > 0) ? static_cast(m_renderer->m_mem.m_clut) : nullptr; - const HashCacheKey key{ HashCacheKey::Create(TEX0, TEXA, m_renderer, clut) }; + const HashCacheKey key{ HashCacheKey::Create(TEX0, TEXA, m_renderer, clut, lod) }; auto it = m_hash_cache.find(key); if (it == m_hash_cache.end()) { // hash and upload texture - src->m_texture = g_gs_device->CreateTexture(tw, th, paltex ? false : mipmap, paltex ? GSTexture::Format::UNorm8 : GSTexture::Format::Color); + src->m_texture = g_gs_device->CreateTexture(tw, th, paltex ? false : (lod != nullptr), paltex ? GSTexture::Format::UNorm8 : GSTexture::Format::Color); PreloadTexture(TEX0, TEXA, m_renderer->m_mem, paltex, src->m_texture, 0); + // upload mips if present + if (lod) + { + const int basemip = lod->x; + const int nmips = lod->y - lod->x + 1; + for (int mip = 1; mip < nmips; mip++) + { + const GIFRegTEX0 MIP_TEX0{m_renderer->GetTex0Layer(basemip + mip)}; + PreloadTexture(MIP_TEX0, TEXA, m_renderer->m_mem, paltex, src->m_texture, mip); + } + } + // insert it into the hash cache HashCacheEntry entry{ src->m_texture, 1, 0 }; it = m_hash_cache.emplace(key, entry).first; @@ -1488,7 +1500,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con } else { - src->m_texture = g_gs_device->CreateTexture(tw, th, mipmap, GSTexture::Format::Color); + src->m_texture = g_gs_device->CreateTexture(tw, th, (lod != nullptr), GSTexture::Format::Color); if (psm.pal > 0) { AttachPaletteToSource(src, psm.pal, false); @@ -1965,13 +1977,9 @@ void GSTextureCache::Source::Flush(u32 count, int layer) void GSTextureCache::Source::PreloadLevel(int level) { // m_TEX0 is adjusted for mips (messy, should be changed). - const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[m_TEX0.PSM]; - const GSVector2i& bs = psm.bs; - const int tw = 1 << m_TEX0.TW; - const int th = 1 << m_TEX0.TH; + const HashType hash = HashTexture(m_renderer, m_TEX0, m_TEXA); // Layer is complete again, regardless of whether the hash matches or not (and we reupload). - const HashType hash = HashTexture(m_renderer, m_TEX0, m_TEXA); const u8 layer_bit = static_cast(1) << level; m_complete_layers |= layer_bit; @@ -2596,7 +2604,7 @@ __fi static GSTextureCache::HashType FinishBlockHash(BlockHashState& st) return XXH3_64bits_digest(&st); } -GSTextureCache::HashType GSTextureCache::HashTexture(GSRenderer* renderer, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA) +static void HashTextureLevel(GSRenderer* renderer, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, BlockHashState& hash_st, u8* temp) { const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM]; const GSVector2i& bs = psm.bs; @@ -2612,8 +2620,6 @@ GSTextureCache::HashType GSTextureCache::HashTexture(GSRenderer* renderer, const // For textures which are smaller than the block size, we expand and then hash. // This is because otherwise we get the padding bytes, which can be random junk. - GSTextureCache::HashType hash; - BlockHashState hash_st; if (tw < bs.x || th < bs.y) { // Expand texture indices. Align to 32 bytes for AVX2. @@ -2622,11 +2628,10 @@ GSTextureCache::HashType GSTextureCache::HashTexture(GSRenderer* renderer, const const GSLocalMemory::readTexture rtx = psm.rtxP; // Use temp buffer for expanding, since we may not need to update. - (renderer->m_mem.*rtx)(off, block_rect, m_temp, pitch, TEXA); + (renderer->m_mem.*rtx)(off, block_rect, temp, pitch, TEXA); // Hash the expanded texture. - u8* ptr = m_temp; - BlockHashReset(hash_st); + u8* ptr = temp; if (pitch == row_size) { BlockHashAccumulate(hash_st, ptr, pitch * static_cast(th)); @@ -2636,7 +2641,6 @@ GSTextureCache::HashType GSTextureCache::HashTexture(GSRenderer* renderer, const for (int y = 0; y < th; y++, ptr += pitch) BlockHashAccumulate(hash_st, ptr, row_size); } - hash = FinishBlockHash(hash_st); } else { @@ -2654,11 +2658,15 @@ GSTextureCache::HashType GSTextureCache::HashTexture(GSRenderer* renderer, const BlockHashAccumulate(hash_st, mem.BlockPtr(bn.value())); } } - - hash = FinishBlockHash(hash_st); } +} - return hash; +GSTextureCache::HashType GSTextureCache::HashTexture(GSRenderer* renderer, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA) +{ + BlockHashState hash_st; + BlockHashReset(hash_st); + HashTextureLevel(renderer, TEX0, TEXA, hash_st, m_temp); + return FinishBlockHash(hash_st); } void GSTextureCache::PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level) @@ -2710,7 +2718,7 @@ GSTextureCache::HashCacheKey::HashCacheKey() TEXA.U64 = 0; } -GSTextureCache::HashCacheKey GSTextureCache::HashCacheKey::Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSRenderer* renderer, const u32* clut) +GSTextureCache::HashCacheKey GSTextureCache::HashCacheKey::Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSRenderer* renderer, const u32* clut, const GSVector2i* lod) { const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM]; @@ -2718,7 +2726,26 @@ GSTextureCache::HashCacheKey GSTextureCache::HashCacheKey::Create(const GIFRegTE ret.TEX0.U64 = TEX0.U64 & 0x00000007FFF00000ULL; ret.TEXA.U64 = (psm.pal == 0 && psm.fmt > 0) ? (TEXA.U64 & 0x000000FF000080FFULL) : 0; ret.CLUTHash = clut ? GSTextureCache::PaletteKeyHash{}({clut, psm.pal}) : 0; - ret.TEX0Hash = HashTexture(renderer, TEX0, TEXA); + + BlockHashState hash_st; + BlockHashReset(hash_st); + + // base level is always hashed + HashTextureLevel(renderer, TEX0, TEXA, hash_st, m_temp); + + if (lod) + { + // hash and combine full mipmaps when enabled + const int basemip = lod->x; + const int nmips = lod->y - lod->x + 1; + for (int i = 1; i < nmips; i++) + { + const GIFRegTEX0 MIP_TEX0{renderer->GetTex0Layer(basemip + i)}; + HashTextureLevel(renderer, MIP_TEX0, TEXA, hash_st, m_temp); + } + } + + ret.TEX0Hash = FinishBlockHash(hash_st); return ret; } diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 3fae8eeba3..c43b9dd351 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -50,7 +50,8 @@ public: HashCacheKey(); - static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSRenderer* renderer, const u32* clut); + static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSRenderer* renderer, const u32* clut, + const GSVector2i* lod); __fi bool operator==(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) == 0; } __fi bool operator!=(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) != 0; } @@ -287,7 +288,7 @@ protected: constexpr static size_t S_SURFACE_OFFSET_CACHE_MAX_SIZE = std::numeric_limits::max(); std::unordered_map m_surface_offset_cache; - Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t = NULL, bool half_right = false, int x_offset = 0, int y_offset = 0, bool mipmap = false); + 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); Target* CreateTarget(const GIFRegTEX0& TEX0, int w, int h, int type, const bool clear); static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level); @@ -307,7 +308,7 @@ public: void RemoveAll(); void RemovePartial(); - Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool mipmap); + Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, const GSVector2i* lod); Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette = false); Target* LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, int type, bool used, u32 fbmask = 0, const bool is_frame = false, const int real_h = 0);