Merge pull request #2097 from mimimi085181/find-textures-by-hash
Search the texture cache for small textures by address and hash
This commit is contained in:
commit
e47e4c677a
|
@ -30,7 +30,8 @@ TextureCache *g_texture_cache;
|
||||||
GC_ALIGNED16(u8 *TextureCache::temp) = nullptr;
|
GC_ALIGNED16(u8 *TextureCache::temp) = nullptr;
|
||||||
size_t TextureCache::temp_size;
|
size_t TextureCache::temp_size;
|
||||||
|
|
||||||
TextureCache::TexCache TextureCache::textures;
|
TextureCache::TexCache TextureCache::textures_by_address;
|
||||||
|
TextureCache::TexCache TextureCache::textures_by_hash;
|
||||||
TextureCache::TexPool TextureCache::texture_pool;
|
TextureCache::TexPool TextureCache::texture_pool;
|
||||||
TextureCache::TCacheEntryBase* TextureCache::bound_textures[8];
|
TextureCache::TCacheEntryBase* TextureCache::bound_textures[8];
|
||||||
|
|
||||||
|
@ -76,11 +77,12 @@ void TextureCache::Invalidate()
|
||||||
{
|
{
|
||||||
UnbindTextures();
|
UnbindTextures();
|
||||||
|
|
||||||
for (auto& tex : textures)
|
for (auto& tex : textures_by_address)
|
||||||
{
|
{
|
||||||
delete tex.second;
|
delete tex.second;
|
||||||
}
|
}
|
||||||
textures.clear();
|
textures_by_address.clear();
|
||||||
|
textures_by_hash.clear();
|
||||||
|
|
||||||
for (auto& rt : texture_pool)
|
for (auto& rt : texture_pool)
|
||||||
{
|
{
|
||||||
|
@ -140,8 +142,8 @@ void TextureCache::OnConfigChanged(VideoConfig& config)
|
||||||
|
|
||||||
void TextureCache::Cleanup(int _frameCount)
|
void TextureCache::Cleanup(int _frameCount)
|
||||||
{
|
{
|
||||||
TexCache::iterator iter = textures.begin();
|
TexCache::iterator iter = textures_by_address.begin();
|
||||||
TexCache::iterator tcend = textures.end();
|
TexCache::iterator tcend = textures_by_address.end();
|
||||||
while (iter != tcend)
|
while (iter != tcend)
|
||||||
{
|
{
|
||||||
if (iter->second->frameCount == FRAMECOUNT_INVALID)
|
if (iter->second->frameCount == FRAMECOUNT_INVALID)
|
||||||
|
@ -152,8 +154,7 @@ void TextureCache::Cleanup(int _frameCount)
|
||||||
// EFB copies living on the host GPU are unrecoverable and thus shouldn't be deleted
|
// EFB copies living on the host GPU are unrecoverable and thus shouldn't be deleted
|
||||||
!iter->second->IsEfbCopy())
|
!iter->second->IsEfbCopy())
|
||||||
{
|
{
|
||||||
FreeTexture(iter->second);
|
iter = RemoveTextureFromCache(iter);
|
||||||
iter = textures.erase(iter);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -184,14 +185,13 @@ void TextureCache::Cleanup(int _frameCount)
|
||||||
void TextureCache::MakeRangeDynamic(u32 start_address, u32 size)
|
void TextureCache::MakeRangeDynamic(u32 start_address, u32 size)
|
||||||
{
|
{
|
||||||
TexCache::iterator
|
TexCache::iterator
|
||||||
iter = textures.begin();
|
iter = textures_by_address.begin();
|
||||||
|
|
||||||
while (iter != textures.end())
|
while (iter != textures_by_address.end())
|
||||||
{
|
{
|
||||||
if (iter->second->OverlapsMemoryRange(start_address, size))
|
if (iter->second->OverlapsMemoryRange(start_address, size))
|
||||||
{
|
{
|
||||||
FreeTexture(iter->second);
|
iter = RemoveTextureFromCache(iter);
|
||||||
iter = textures.erase(iter);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -288,6 +288,7 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
|
|
||||||
// Hash assigned to texcache entry (also used to generate filenames used for texture dumping and custom texture lookup)
|
// Hash assigned to texcache entry (also used to generate filenames used for texture dumping and custom texture lookup)
|
||||||
u64 tex_hash = TEXHASH_INVALID;
|
u64 tex_hash = TEXHASH_INVALID;
|
||||||
|
u64 full_hash = TEXHASH_INVALID;
|
||||||
|
|
||||||
u32 full_format = texformat;
|
u32 full_format = texformat;
|
||||||
|
|
||||||
|
@ -311,17 +312,22 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
// TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data from the low tmem bank than it should)
|
// TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data from the low tmem bank than it should)
|
||||||
tex_hash = GetHash64(src_data, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
tex_hash = GetHash64(src_data, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||||
u32 palette_size = 0;
|
u32 palette_size = 0;
|
||||||
u64 tlut_hash = 0;
|
|
||||||
if (isPaletteTexture)
|
if (isPaletteTexture)
|
||||||
{
|
{
|
||||||
palette_size = TexDecoder_GetPaletteSize(texformat);
|
palette_size = TexDecoder_GetPaletteSize(texformat);
|
||||||
tlut_hash = GetHash64(&texMem[tlutaddr], palette_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
full_hash = tex_hash ^ GetHash64(&texMem[tlutaddr], palette_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
full_hash = tex_hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GPUs don't like when the specified mipmap count would require more than one 1x1-sized LOD in the mipmap chain
|
// GPUs don't like when the specified mipmap count would require more than one 1x1-sized LOD in the mipmap chain
|
||||||
// e.g. 64x64 with 7 LODs would have the mipmap chain 64x64,32x32,16x16,8x8,4x4,2x2,1x1,0x0, so we limit the mipmap count to 6 there
|
// e.g. 64x64 with 7 LODs would have the mipmap chain 64x64,32x32,16x16,8x8,4x4,2x2,1x1,0x0, so we limit the mipmap count to 6 there
|
||||||
tex_levels = std::min<u32>(IntLog2(std::max(width, height)) + 1, tex_levels);
|
tex_levels = std::min<u32>(IntLog2(std::max(width, height)) + 1, tex_levels);
|
||||||
|
|
||||||
|
// Search the texture cache for textures by address
|
||||||
|
//
|
||||||
// Find all texture cache entries for the current texture address, and decide whether to use one of
|
// Find all texture cache entries for the current texture address, and decide whether to use one of
|
||||||
// them, or to create a new one
|
// them, or to create a new one
|
||||||
//
|
//
|
||||||
|
@ -346,11 +352,11 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
//
|
//
|
||||||
// For efb copies, the entry created in CopyRenderTargetToTexture always has to be used, or else it was
|
// For efb copies, the entry created in CopyRenderTargetToTexture always has to be used, or else it was
|
||||||
// done in vain.
|
// done in vain.
|
||||||
std::pair <TexCache::iterator, TexCache::iterator> iter_range = textures.equal_range(address);
|
std::pair<TexCache::iterator, TexCache::iterator> iter_range = textures_by_address.equal_range((u64)address);
|
||||||
TexCache::iterator iter = iter_range.first;
|
TexCache::iterator iter = iter_range.first;
|
||||||
TexCache::iterator oldest_entry = iter;
|
TexCache::iterator oldest_entry = iter;
|
||||||
int temp_frameCount = 0x7fffffff;
|
int temp_frameCount = 0x7fffffff;
|
||||||
TexCache::iterator unconverted_copy = textures.end();
|
TexCache::iterator unconverted_copy = textures_by_address.end();
|
||||||
|
|
||||||
while (iter != iter_range.second)
|
while (iter != iter_range.second)
|
||||||
{
|
{
|
||||||
|
@ -383,14 +389,14 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
// something weird where the copy could become useful in the future, but in
|
// something weird where the copy could become useful in the future, but in
|
||||||
// practice it doesn't happen.
|
// practice it doesn't happen.
|
||||||
FreeTexture(entry);
|
FreeTexture(entry);
|
||||||
iter = textures.erase(iter);
|
iter = textures_by_address.erase(iter);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// For normal textures, all texture parameters need to match
|
// For normal textures, all texture parameters need to match
|
||||||
if (entry->hash == (tex_hash ^ tlut_hash) && entry->format == full_format && entry->native_levels >= tex_levels &&
|
if (entry->hash == full_hash && entry->format == full_format && entry->native_levels >= tex_levels &&
|
||||||
entry->native_width == nativeW && entry->native_height == nativeH)
|
entry->native_width == nativeW && entry->native_height == nativeH)
|
||||||
{
|
{
|
||||||
return ReturnEntry(stage, entry);
|
return ReturnEntry(stage, entry);
|
||||||
|
@ -406,7 +412,7 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unconverted_copy != textures.end())
|
if (unconverted_copy != textures_by_address.end())
|
||||||
{
|
{
|
||||||
// Perform palette decoding.
|
// Perform palette decoding.
|
||||||
TCacheEntryBase *entry = unconverted_copy->second;
|
TCacheEntryBase *entry = unconverted_copy->second;
|
||||||
|
@ -420,21 +426,42 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
|
|
||||||
decoded_entry->SetGeneralParameters(address, texture_size, full_format);
|
decoded_entry->SetGeneralParameters(address, texture_size, full_format);
|
||||||
decoded_entry->SetDimensions(entry->native_width, entry->native_height, 1);
|
decoded_entry->SetDimensions(entry->native_width, entry->native_height, 1);
|
||||||
decoded_entry->SetHashes(tex_hash ^ tlut_hash);
|
decoded_entry->SetHashes(full_hash);
|
||||||
decoded_entry->frameCount = FRAMECOUNT_INVALID;
|
decoded_entry->frameCount = FRAMECOUNT_INVALID;
|
||||||
decoded_entry->is_efb_copy = false;
|
decoded_entry->is_efb_copy = false;
|
||||||
|
|
||||||
g_texture_cache->ConvertTexture(decoded_entry, entry, &texMem[tlutaddr], (TlutFormat)tlutfmt);
|
g_texture_cache->ConvertTexture(decoded_entry, entry, &texMem[tlutaddr], (TlutFormat)tlutfmt);
|
||||||
textures.insert(TexCache::value_type(address, decoded_entry));
|
textures_by_address.insert(TexCache::value_type((u64)address, decoded_entry));
|
||||||
return ReturnEntry(stage, decoded_entry);
|
return ReturnEntry(stage, decoded_entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search the texture cache for normal textures by hash
|
||||||
|
//
|
||||||
|
// If the texture was fully hashed, the address does not need to match. Identical duplicate textures cause unnecessary slowdowns
|
||||||
|
// Example: Tales of Symphonia (GC) uses over 500 small textures in menus, but only around 70 different ones
|
||||||
|
if (g_ActiveConfig.iSafeTextureCache_ColorSamples == 0 ||
|
||||||
|
std::max(texture_size, palette_size) <= (u32)g_ActiveConfig.iSafeTextureCache_ColorSamples * 8)
|
||||||
|
{
|
||||||
|
iter_range = textures_by_hash.equal_range(full_hash);
|
||||||
|
iter = iter_range.first;
|
||||||
|
while (iter != iter_range.second)
|
||||||
|
{
|
||||||
|
TCacheEntryBase* entry = iter->second;
|
||||||
|
// All parameters, except the address, need to match here
|
||||||
|
if (entry->format == full_format && entry->native_levels >= tex_levels &&
|
||||||
|
entry->native_width == nativeW && entry->native_height == nativeH)
|
||||||
|
{
|
||||||
|
return ReturnEntry(stage, entry);
|
||||||
|
}
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If at least one entry was not used for the same frame, overwrite the oldest one
|
// If at least one entry was not used for the same frame, overwrite the oldest one
|
||||||
if (temp_frameCount != 0x7fffffff)
|
if (temp_frameCount != 0x7fffffff)
|
||||||
{
|
{
|
||||||
// pool this texture and make a new one later
|
// pool this texture and make a new one later
|
||||||
FreeTexture(oldest_entry->second);
|
RemoveTextureFromCache(oldest_entry);
|
||||||
textures.erase(oldest_entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<HiresTexture> hires_tex;
|
std::shared_ptr<HiresTexture> hires_tex;
|
||||||
|
@ -488,11 +515,16 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
TCacheEntryBase* entry = AllocateTexture(config);
|
TCacheEntryBase* entry = AllocateTexture(config);
|
||||||
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
|
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
|
||||||
|
|
||||||
textures.insert(TexCache::value_type(address, entry));
|
textures_by_address.insert(TexCache::value_type((u64)address, entry));
|
||||||
|
if (g_ActiveConfig.iSafeTextureCache_ColorSamples == 0 ||
|
||||||
|
std::max(texture_size, palette_size) <= (u32)g_ActiveConfig.iSafeTextureCache_ColorSamples * 8)
|
||||||
|
{
|
||||||
|
entry->textures_by_hash_iter = textures_by_hash.insert(TexCache::value_type(full_hash, entry));
|
||||||
|
}
|
||||||
|
|
||||||
entry->SetGeneralParameters(address, texture_size, full_format);
|
entry->SetGeneralParameters(address, texture_size, full_format);
|
||||||
entry->SetDimensions(nativeW, nativeH, tex_levels);
|
entry->SetDimensions(nativeW, nativeH, tex_levels);
|
||||||
entry->hash = tex_hash ^ tlut_hash;
|
entry->hash = full_hash;
|
||||||
entry->is_efb_copy = false;
|
entry->is_efb_copy = false;
|
||||||
entry->is_custom_tex = hires_tex != nullptr;
|
entry->is_custom_tex = hires_tex != nullptr;
|
||||||
|
|
||||||
|
@ -557,7 +589,7 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
INCSTAT(stats.numTexturesUploaded);
|
INCSTAT(stats.numTexturesUploaded);
|
||||||
SETSTAT(stats.numTexturesAlive, textures.size());
|
SETSTAT(stats.numTexturesAlive, textures_by_address.size());
|
||||||
|
|
||||||
return ReturnEntry(stage, entry);
|
return ReturnEntry(stage, entry);
|
||||||
}
|
}
|
||||||
|
@ -849,12 +881,11 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
|
||||||
unsigned int scaled_tex_h = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledY(tex_h) : tex_h;
|
unsigned int scaled_tex_h = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledY(tex_h) : tex_h;
|
||||||
|
|
||||||
// remove all texture cache entries at dstAddr
|
// remove all texture cache entries at dstAddr
|
||||||
std::pair <TexCache::iterator, TexCache::iterator> iter_range = textures.equal_range(dstAddr);
|
std::pair<TexCache::iterator, TexCache::iterator> iter_range = textures_by_address.equal_range((u64)dstAddr);
|
||||||
TexCache::iterator iter = iter_range.first;
|
TexCache::iterator iter = iter_range.first;
|
||||||
while (iter != iter_range.second)
|
while (iter != iter_range.second)
|
||||||
{
|
{
|
||||||
FreeTexture(iter->second);
|
iter = RemoveTextureFromCache(iter);
|
||||||
iter = textures.erase(iter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the texture
|
// create the texture
|
||||||
|
@ -884,21 +915,38 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
|
||||||
count++), 0);
|
count++), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
textures.insert(TexCache::value_type(dstAddr, entry));
|
textures_by_address.insert(TexCache::value_type((u64)dstAddr, entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureCache::TCacheEntryBase* TextureCache::AllocateTexture(const TCacheEntryConfig& config)
|
TextureCache::TCacheEntryBase* TextureCache::AllocateTexture(const TCacheEntryConfig& config)
|
||||||
{
|
{
|
||||||
TexPool::iterator iter = texture_pool.find(config);
|
TexPool::iterator iter = texture_pool.find(config);
|
||||||
|
TextureCache::TCacheEntryBase* entry;
|
||||||
if (iter != texture_pool.end())
|
if (iter != texture_pool.end())
|
||||||
{
|
{
|
||||||
TextureCache::TCacheEntryBase* entry = iter->second;
|
entry = iter->second;
|
||||||
texture_pool.erase(iter);
|
texture_pool.erase(iter);
|
||||||
return entry;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entry = g_texture_cache->CreateTexture(config);
|
||||||
|
INCSTAT(stats.numTexturesCreated);
|
||||||
}
|
}
|
||||||
|
|
||||||
INCSTAT(stats.numTexturesCreated);
|
entry->textures_by_hash_iter = textures_by_address.end();
|
||||||
return g_texture_cache->CreateTexture(config);
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureCache::TexCache::iterator TextureCache::RemoveTextureFromCache(TexCache::iterator iter)
|
||||||
|
{
|
||||||
|
if (iter->second->textures_by_hash_iter != textures_by_address.end())
|
||||||
|
{
|
||||||
|
textures_by_hash.erase(iter->second->textures_by_hash_iter);
|
||||||
|
iter->second->textures_by_hash_iter = textures_by_address.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeTexture(iter->second);
|
||||||
|
return textures_by_address.erase(iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::FreeTexture(TCacheEntryBase* entry)
|
void TextureCache::FreeTexture(TCacheEntryBase* entry)
|
||||||
|
|
|
@ -60,6 +60,8 @@ public:
|
||||||
// used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames
|
// used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames
|
||||||
int frameCount;
|
int frameCount;
|
||||||
|
|
||||||
|
// Keep an iterator to the entry in textures_by_hash, so it does not need to be searched when removing the cache entry
|
||||||
|
std::multimap<u64, TCacheEntryBase*>::iterator textures_by_hash_iter;
|
||||||
|
|
||||||
void SetGeneralParameters(u32 _addr, u32 _size, u32 _format)
|
void SetGeneralParameters(u32 _addr, u32 _size, u32 _format)
|
||||||
{
|
{
|
||||||
|
@ -131,18 +133,20 @@ protected:
|
||||||
static size_t temp_size;
|
static size_t temp_size;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
typedef std::multimap<u64, TCacheEntryBase*> TexCache;
|
||||||
|
typedef std::unordered_multimap<TCacheEntryConfig, TCacheEntryBase*, TCacheEntryConfig::Hasher> TexPool;
|
||||||
|
|
||||||
static void DumpTexture(TCacheEntryBase* entry, std::string basename, unsigned int level);
|
static void DumpTexture(TCacheEntryBase* entry, std::string basename, unsigned int level);
|
||||||
static void CheckTempSize(size_t required_size);
|
static void CheckTempSize(size_t required_size);
|
||||||
|
|
||||||
static TCacheEntryBase* AllocateTexture(const TCacheEntryConfig& config);
|
static TCacheEntryBase* AllocateTexture(const TCacheEntryConfig& config);
|
||||||
|
static TexCache::iterator RemoveTextureFromCache(TexCache::iterator t_iter);
|
||||||
static void FreeTexture(TCacheEntryBase* entry);
|
static void FreeTexture(TCacheEntryBase* entry);
|
||||||
|
|
||||||
static TCacheEntryBase* ReturnEntry(unsigned int stage, TCacheEntryBase* entry);
|
static TCacheEntryBase* ReturnEntry(unsigned int stage, TCacheEntryBase* entry);
|
||||||
|
|
||||||
typedef std::multimap<u32, TCacheEntryBase*> TexCache;
|
static TexCache textures_by_address;
|
||||||
typedef std::unordered_multimap<TCacheEntryConfig, TCacheEntryBase*, TCacheEntryConfig::Hasher> TexPool;
|
static TexCache textures_by_hash;
|
||||||
|
|
||||||
static TexCache textures;
|
|
||||||
static TexPool texture_pool;
|
static TexPool texture_pool;
|
||||||
static TCacheEntryBase* bound_textures[8];
|
static TCacheEntryBase* bound_textures[8];
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue