diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index f25212fc7e..19b8f24114 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) { @@ -657,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); @@ -674,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); @@ -686,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); } @@ -731,6 +732,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); @@ -763,28 +795,37 @@ 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() { 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 *)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_ = QuickClutHash((const u8 *)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) { + ConvertColors(clutBufConverted_, clutBufRaw_, 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; @@ -805,6 +846,8 @@ void TextureCache::UpdateCurrentClut() { } } } + + clutLastFormat_ = clutFormat; } template @@ -836,12 +879,11 @@ void TextureCache::SetTexture() { u32 clutformat, cluthash; if (hasClut) { - if (clutDirty_) { + clutformat = gstate.clutformat & 3; + if (clutLastFormat_ != clutformat) { // We update here because the clut format can be specified after the load. UpdateCurrentClut(); - clutDirty_ = false; } - clutformat = gstate.clutformat & 3; cluthash = GetCurrentClutHash() ^ gstate.clutformat; cachekey |= (u64)cluthash << 32; } else { @@ -849,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)); @@ -892,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; @@ -914,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) { @@ -991,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); } @@ -1188,7 +1233,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: @@ -1209,7 +1254,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: @@ -1229,7 +1274,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; @@ -1252,7 +1297,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; @@ -1274,7 +1319,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; diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index cf1c8964f5..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. @@ -126,9 +126,12 @@ private: SimpleBuf tmpTexBufRearrange; - bool clutDirty_; + u8 clutLastFormat_; + u32 *clutBufRaw_; + u32 *clutBufConverted_; 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_;