mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-12 00:59:41 +01:00
Merge pull request #1936 from unknownbrackets/texcache
Improve CLUT load performance, tweak hashing
This commit is contained in:
commit
e7da2c03fa
2 files changed
+118
-70
No files matched your search
+113
-68
@@ -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 <typename T>
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<u32> 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_;
|
||||
|
||||
Reference in new issue
Block a user