From c53386244d962fb3d5aa605bd75153a6123a4b65 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 May 2013 12:41:36 -0700 Subject: [PATCH 1/5] Don't hash the clut when it's not even used. Many games load the clut even when it wasn't changed, and we don't flush between when this happens. There shouldn't be any need to rehash the clut either. Technically, the clut could have actually changed, but this would generally require them to clear the dcache or something in a way the GE probably doesn't support. --- GPU/GLES/TextureCache.cpp | 8 ++++---- GPU/GLES/TextureCache.h | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index f25212fc7e..67695b6126 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -769,12 +769,11 @@ inline bool TextureCache::TexCacheEntry::Matches(u16 dim2, u32 hash2, u8 format2 void TextureCache::LoadClut() { u32 clutAddr = GetClutAddr(); - u32 clutTotalBytes = (gstate.loadclut & 0x3f) * 32; + clutTotalBytes_ = (gstate.loadclut & 0x3f) * 32; if (Memory::IsValidAddress(clutAddr)) { - Memory::Memcpy((u8 *)clutBuf_, clutAddr, clutTotalBytes); - clutHash_ = CityHash32((const char *)clutBuf_, clutTotalBytes); + Memory::Memcpy((u8 *)clutBuf_, clutAddr, clutTotalBytes_); } else { - memset(clutBuf_, 0xFF, clutTotalBytes); + memset(clutBuf_, 0xFF, clutTotalBytes_); clutHash_ = 0; } clutDirty_ = true; @@ -838,6 +837,7 @@ void TextureCache::SetTexture() { if (hasClut) { if (clutDirty_) { // We update here because the clut format can be specified after the load. + clutHash_ = CityHash32((const char *)clutBuf_, clutTotalBytes_); UpdateCurrentClut(); clutDirty_ = false; } diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index cf1c8964f5..b67b075442 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -129,6 +129,7 @@ private: bool clutDirty_; u32 *clutBuf_; u32 clutHash_; + u32 clutTotalBytes_; // True if the clut is just alpha values in the same order (RGBA4444-bit only.) bool clutAlphaLinear_; u16 clutAlphaLinearColor_; From 5dabdd3651de63c271a14afa165047ece91ba9fc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 May 2013 12:45:53 -0700 Subject: [PATCH 2/5] Make sure to update the CLUT if format changes. Otherwise we throw away the loaded clut, which is no good. --- GPU/GLES/TextureCache.cpp | 43 +++++++++++++++++++++++++-------------- GPU/GLES/TextureCache.h | 4 +++- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 67695b6126..76e1d65efd 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -57,18 +57,20 @@ static inline u32 GetLevelBufw(int level, u32 texaddr) { return gstate.texbufwidth[level] & 0x7FF; } -TextureCache::TextureCache() : clearCacheNextFrame_(false), lowMemoryMode_(false), clutDirty_(false) { +TextureCache::TextureCache() : clearCacheNextFrame_(false), lowMemoryMode_(false), clutBuf_(NULL) { lastBoundTexture = -1; // This is 5MB of temporary storage. Might be possible to shrink it. tmpTexBuf32.resize(1024 * 512); // 2MB tmpTexBuf16.resize(1024 * 512); // 1MB tmpTexBufRearrange.resize(1024 * 512); // 2MB - clutBuf_ = new u32[4096]; // 16KB + clutBufConverted_ = new u32[4096]; // 16KB + clutBufRaw_ = new u32[4096]; // 16KB glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropyLevel); } TextureCache::~TextureCache() { - delete [] clutBuf_; + delete [] clutBufConverted_; + delete [] clutBufRaw_; } void TextureCache::Clear(bool delete_them) { @@ -771,19 +773,30 @@ void TextureCache::LoadClut() { u32 clutAddr = GetClutAddr(); clutTotalBytes_ = (gstate.loadclut & 0x3f) * 32; if (Memory::IsValidAddress(clutAddr)) { - Memory::Memcpy((u8 *)clutBuf_, clutAddr, clutTotalBytes_); + Memory::Memcpy((u8 *)clutBufRaw_, clutAddr, clutTotalBytes_); } else { - memset(clutBuf_, 0xFF, clutTotalBytes_); + memset(clutBufRaw_, 0xFF, clutTotalBytes_); clutHash_ = 0; } - clutDirty_ = true; + // Reload the clut next time. + clutLastFormat_ = 0xFF; } void TextureCache::UpdateCurrentClut() { + // 0xFF is an invalid format, it means not yet hashed or updated. + if (clutLastFormat_ == 0xFF) { + clutHash_ = CityHash32((const char *)clutBufRaw_, clutTotalBytes_); + } + GEPaletteFormat clutFormat = (GEPaletteFormat)(gstate.clutformat & 3); - const u32 clutColorBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? 4 : 2; - u32 clutTotalBytes = (gstate.loadclut & 0x3f) * 32; - convertColors((u8 *)clutBuf_, getClutDestFormat(clutFormat), clutTotalBytes / clutColorBytes); + // Avoid a copy when we don't need to convert colors. + if (clutFormat != GE_CMODE_32BIT_ABGR8888) { + memcpy(clutBufConverted_, clutBufRaw_, clutTotalBytes_); + convertColors((u8 *)clutBufConverted_, getClutDestFormat(clutFormat), clutTotalBytes_ / sizeof(u16)); + clutBuf_ = clutBufConverted_; + } else { + clutBuf_ = clutBufRaw_; + } // Special optimization: fonts typically draw clut4 with just alpha values in a single color. clutAlphaLinear_ = false; @@ -804,6 +817,8 @@ void TextureCache::UpdateCurrentClut() { } } } + + clutLastFormat_ = clutFormat; } template @@ -835,13 +850,11 @@ void TextureCache::SetTexture() { u32 clutformat, cluthash; if (hasClut) { - if (clutDirty_) { - // We update here because the clut format can be specified after the load. - clutHash_ = CityHash32((const char *)clutBuf_, clutTotalBytes_); - UpdateCurrentClut(); - clutDirty_ = false; - } clutformat = gstate.clutformat & 3; + if (clutLastFormat_ != clutformat) { + // We update here because the clut format can be specified after the load. + UpdateCurrentClut(); + } cluthash = GetCurrentClutHash() ^ gstate.clutformat; cachekey |= (u64)cluthash << 32; } else { diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index b67b075442..ee730567e0 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -126,7 +126,9 @@ private: SimpleBuf tmpTexBufRearrange; - bool clutDirty_; + u8 clutLastFormat_; + u32 *clutBufRaw_; + u32 *clutBufConverted_; u32 *clutBuf_; u32 clutHash_; u32 clutTotalBytes_; From d2a52f48e54603d0ec82883d88188da3fed23686 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 May 2013 13:16:57 -0700 Subject: [PATCH 3/5] Switch to a weaker, but faster clut hash. --- GPU/GLES/TextureCache.cpp | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 76e1d65efd..a9207e5034 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -733,6 +733,37 @@ static inline u32 MiniHash(const u32 *ptr) { return ptr[0]; } +static inline u32 QuickClutHash(const u8 *clut, u32 bytes) { + // CLUTs always come in multiples of 32 bytes, can't load them any other way. + _dbg_assert_msg_(G3D, (bytes & 31) == 0, "CLUT should always have a multiple of 32 bytes."); + + const u32 prime = 2246822519U; + u32 hash = 0; +#ifdef _M_SSE + if ((((u32)(intptr_t)clut) & 0xf) == 0) { + __m128i cursor = _mm_set1_epi32(0); + const __m128i mult = _mm_set1_epi32(prime); + const __m128i *p = (const __m128i *)clut; + for (u32 i = 0; i < bytes / 16; ++i) { + cursor = _mm_add_epi32(cursor, _mm_mul_epi32(_mm_load_si128(&p[i]), mult)); + } + // Add the four parts into the low i32. + cursor = _mm_add_epi32(cursor, _mm_srli_si128(cursor, 8)); + cursor = _mm_add_epi32(cursor, _mm_srli_si128(cursor, 4)); + hash = _mm_cvtsi128_si32(cursor); + } else { +#else + // TODO: ARM NEON implementation (using CPUDetect to be sure it has NEON.) + { +#endif + for (const u32 *p = (u32 *)clut, *end = (u32 *)(clut + bytes); p < end; ) { + hash += *p++ * prime; + } + } + + return hash; +} + static inline u32 QuickTexHash(u32 addr, int bufw, int w, int h, u32 format) { const u32 sizeInRAM = (bitsPerPixel[format < 11 ? format : 0] * bufw * h) / 8; const u32 *checkp = (const u32 *) Memory::GetPointer(addr); @@ -785,7 +816,7 @@ void TextureCache::LoadClut() { void TextureCache::UpdateCurrentClut() { // 0xFF is an invalid format, it means not yet hashed or updated. if (clutLastFormat_ == 0xFF) { - clutHash_ = CityHash32((const char *)clutBufRaw_, clutTotalBytes_); + clutHash_ = QuickClutHash((const u8 *)clutBufRaw_, clutTotalBytes_); } GEPaletteFormat clutFormat = (GEPaletteFormat)(gstate.clutformat & 3); From 01e1a8093acc03e3db09fa66300ce68cb8905d44 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 May 2013 13:31:58 -0700 Subject: [PATCH 4/5] Allow ConvertColors() to work as a copy op. --- GPU/GLES/TextureCache.cpp | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index a9207e5034..a1a56f2184 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -659,15 +659,16 @@ static void decodeDXT5Block(u32 *dst, const DXT5Block *src, int pitch) { } } -static void convertColors(u8 *finalBuf, GLuint dstFmt, int numPixels) { +static void ConvertColors(void *dstBuf, const void *srcBuf, GLuint dstFmt, int numPixels) { + const u32 *src = (const u32 *)srcBuf; + u32 *dst = (u32 *)dstBuf; // TODO: All these can be further sped up with SSE or NEON. switch (dstFmt) { case GL_UNSIGNED_SHORT_4_4_4_4: { - u32 *p = (u32 *)finalBuf; for (int i = 0; i < (numPixels + 1) / 2; i++) { - u32 c = p[i]; - p[i] = ((c >> 12) & 0x000F000F) | + u32 c = src[i]; + dst[i] = ((c >> 12) & 0x000F000F) | ((c >> 4) & 0x00F000F0) | ((c << 4) & 0x0F000F00) | ((c << 12) & 0xF000F000); @@ -676,10 +677,9 @@ static void convertColors(u8 *finalBuf, GLuint dstFmt, int numPixels) { break; case GL_UNSIGNED_SHORT_5_5_5_1: { - u32 *p = (u32 *)finalBuf; for (int i = 0; i < (numPixels + 1) / 2; i++) { - u32 c = p[i]; - p[i] = ((c >> 15) & 0x00010001) | + u32 c = src[i]; + dst[i] = ((c >> 15) & 0x00010001) | ((c >> 9) & 0x003E003E) | ((c << 1) & 0x07C007C0) | ((c << 11) & 0xF800F800); @@ -688,10 +688,9 @@ static void convertColors(u8 *finalBuf, GLuint dstFmt, int numPixels) { break; case GL_UNSIGNED_SHORT_5_6_5: { - u32 *p = (u32 *)finalBuf; for (int i = 0; i < (numPixels + 1) / 2; i++) { - u32 c = p[i]; - p[i] = ((c >> 11) & 0x001F001F) | + u32 c = src[i]; + dst[i] = ((c >> 11) & 0x001F001F) | ((c >> 0) & 0x07E007E0) | ((c << 11) & 0xF800F800); } @@ -822,8 +821,7 @@ void TextureCache::UpdateCurrentClut() { GEPaletteFormat clutFormat = (GEPaletteFormat)(gstate.clutformat & 3); // Avoid a copy when we don't need to convert colors. if (clutFormat != GE_CMODE_32BIT_ABGR8888) { - memcpy(clutBufConverted_, clutBufRaw_, clutTotalBytes_); - convertColors((u8 *)clutBufConverted_, getClutDestFormat(clutFormat), clutTotalBytes_ / sizeof(u16)); + ConvertColors(clutBufConverted_, clutBufRaw_, getClutDestFormat(clutFormat), clutTotalBytes_ / sizeof(u16)); clutBuf_ = clutBufConverted_; } else { clutBuf_ = clutBufRaw_; @@ -1232,7 +1230,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 tmpTexBuf32.resize(std::max(bufw, w) * h); finalBuf = UnswizzleFromMem(texaddr, bufw, 2, level); } - convertColors((u8*)finalBuf, dstFmt, bufw * h); + ConvertColors(finalBuf, finalBuf, dstFmt, bufw * h); break; case GE_TFMT_8888: @@ -1253,7 +1251,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 tmpTexBuf32.resize(std::max(bufw, w) * h); finalBuf = UnswizzleFromMem(texaddr, bufw, 4, level); } - convertColors((u8*)finalBuf, dstFmt, bufw * h); + ConvertColors(finalBuf, finalBuf, dstFmt, bufw * h); break; case GE_TFMT_DXT1: @@ -1273,7 +1271,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 } } finalBuf = tmpTexBuf32.data(); - convertColors((u8*)finalBuf, dstFmt, bufw * h); + ConvertColors(finalBuf, finalBuf, dstFmt, bufw * h); w = (w + 3) & ~3; } break; @@ -1296,7 +1294,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 } w = (w + 3) & ~3; finalBuf = tmpTexBuf32.data(); - convertColors((u8*)finalBuf, dstFmt, bufw * h); + ConvertColors(finalBuf, finalBuf, dstFmt, bufw * h); } break; @@ -1318,7 +1316,7 @@ void *TextureCache::DecodeTextureLevel(u8 format, u8 clutformat, int level, u32 } w = (w + 3) & ~3; finalBuf = tmpTexBuf32.data(); - convertColors((u8*)finalBuf, dstFmt, bufw * h); + ConvertColors(finalBuf, finalBuf, dstFmt, bufw * h); } break; From f042894c3e09db532891db3f9850844ffa39aaae Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 May 2013 13:44:47 -0700 Subject: [PATCH 5/5] Don't bypass the secondary cache on minihash fail. --- GPU/GLES/TextureCache.cpp | 81 ++++++++++++++++++++------------------- GPU/GLES/TextureCache.h | 2 +- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index a1a56f2184..19b8f24114 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -795,8 +795,8 @@ static inline u32 QuickTexHash(u32 addr, int bufw, int w, int h, u32 format) { return check; } -inline bool TextureCache::TexCacheEntry::Matches(u16 dim2, u32 hash2, u8 format2, int maxLevel2) { - return dim == dim2 && hash == hash2 && format == format2 && maxLevel == maxLevel2; +inline bool TextureCache::TexCacheEntry::Matches(u16 dim2, u8 format2, int maxLevel2) { + return dim == dim2 && format == format2 && maxLevel == maxLevel2; } void TextureCache::LoadClut() { @@ -891,6 +891,9 @@ void TextureCache::SetTexture() { cluthash = 0; } + int w = 1 << (gstate.texsize[0] & 0xf); + int h = 1 << ((gstate.texsize[0] >> 8) & 0xf); + int bufw = GetLevelBufw(0, texaddr); int maxLevel = ((gstate.texmode >> 16) & 0x7); u32 texhash = MiniHash((const u32 *)Memory::GetPointer(texaddr)); @@ -934,7 +937,7 @@ void TextureCache::SetTexture() { //Validate the texture here (width, height etc) int dim = gstate.texsize[0] & 0xF0F; - bool match = entry->Matches(dim, texhash, format, maxLevel); + bool match = entry->Matches(dim, format, maxLevel); bool rehash = (entry->status & TexCacheEntry::STATUS_MASK) == TexCacheEntry::STATUS_UNRELIABLE; bool doDelete = true; @@ -956,46 +959,50 @@ void TextureCache::SetTexture() { rehash = true; } + bool hashFail = false; + if (texhash != entry->hash) { + fullhash = QuickTexHash(texaddr, bufw, w, h, format); + hashFail = true; + rehash = false; + } + if (rehash && (entry->status & TexCacheEntry::STATUS_MASK) != TexCacheEntry::STATUS_RELIABLE) { - int w = 1 << (gstate.texsize[0] & 0xf); - int h = 1 << ((gstate.texsize[0] >> 8) & 0xf); - int bufw = GetLevelBufw(0, texaddr); fullhash = QuickTexHash(texaddr, bufw, w, h, format); if (fullhash != entry->fullhash) { - entry->status |= TexCacheEntry::STATUS_UNRELIABLE; - entry->numFrames = 0; - - // Don't give up just yet. Let's try the secondary cache if it's been invalidated before. - // If it's failed a bunch of times, then the second cache is just wasting time and VRAM. - if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) { - u64 secondKey = fullhash | (u64)cluthash << 32; - TexCache::iterator secondIter = secondCache.find(secondKey); - if (secondIter != secondCache.end()) { - TexCacheEntry *secondEntry = &secondIter->second; - if (secondEntry->Matches(dim, texhash, format, maxLevel)) { - // Reset the numInvalidated value lower, we got a match. - if (entry->numInvalidated > 8) { - --entry->numInvalidated; - } - entry = secondEntry; - } else { - match = false; - } - } else { - match = false; - - secondKey = entry->fullhash | (u64)entry->cluthash << 32; - secondCache[secondKey] = *entry; - doDelete = false; - } - } else { - match = false; - } + hashFail = true; } else if ((entry->status & TexCacheEntry::STATUS_MASK) == TexCacheEntry::STATUS_UNRELIABLE && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) { // Reset to STATUS_HASHING. entry->status &= ~TexCacheEntry::STATUS_MASK; } } + + if (hashFail) { + match = false; + entry->status |= TexCacheEntry::STATUS_UNRELIABLE; + entry->numFrames = 0; + + // Don't give up just yet. Let's try the secondary cache if it's been invalidated before. + // If it's failed a bunch of times, then the second cache is just wasting time and VRAM. + if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) { + u64 secondKey = fullhash | (u64)cluthash << 32; + TexCache::iterator secondIter = secondCache.find(secondKey); + if (secondIter != secondCache.end()) { + TexCacheEntry *secondEntry = &secondIter->second; + if (secondEntry->Matches(dim, format, maxLevel)) { + // Reset the numInvalidated value lower, we got a match. + if (entry->numInvalidated > 8) { + --entry->numInvalidated; + } + entry = secondEntry; + match = true; + } + } else { + secondKey = entry->fullhash | (u64)entry->cluthash << 32; + secondCache[secondKey] = *entry; + doDelete = false; + } + } + } } if (match) { @@ -1033,10 +1040,6 @@ void TextureCache::SetTexture() { entry->status = TexCacheEntry::STATUS_HASHING; } - int w = 1 << (gstate.texsize[0] & 0xf); - int h = 1 << ((gstate.texsize[0] >> 8) & 0xf); - - int bufw = GetLevelBufw(0, texaddr); if ((bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && texaddr >= PSP_GetUserMemoryBase()) { ERROR_LOG_REPORT(HLE, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff); } diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index ee730567e0..76cd75032b 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -96,7 +96,7 @@ private: bool sClamp; bool tClamp; - bool Matches(u16 dim2, u32 hash2, u8 format2, int maxLevel2); + bool Matches(u16 dim2, u8 format2, int maxLevel2); }; void Decimate(); // Run this once per frame to get rid of old textures.