From 1a26699607d8fe7bf1a989eabed0b0f4a81e216c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 11:17:55 -0700 Subject: [PATCH 01/10] Refactor texture loading to move things around. --- GPU/Common/TextureCacheCommon.cpp | 16 +-- GPU/Common/TextureCacheCommon.h | 2 +- GPU/Directx9/TextureCacheDX9.cpp | 2 +- GPU/GLES/TextureCache.cpp | 162 +++++++++++++++++------------- GPU/GLES/TextureCache.h | 6 ++ 5 files changed, 108 insertions(+), 80 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 4f423d4604..194efca153 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -125,25 +125,25 @@ void TextureCacheCommon::GetSamplingParams(int &minFilt, int &magFilt, bool &sCl } } -void TextureCacheCommon::UpdateMaxSeenV(bool throughMode) { +void TextureCacheCommon::UpdateMaxSeenV(TexCacheEntry *entry, bool throughMode) { // If the texture is >= 512 pixels tall... - if (nextTexture_->dim >= 0x900) { + if (entry->dim >= 0x900) { // Texture scale/offset and gen modes don't apply in through. // So we can optimize how much of the texture we look at. if (throughMode) { - if (nextTexture_->maxSeenV == 0 && gstate_c.vertBounds.maxV > 0) { + if (entry->maxSeenV == 0 && gstate_c.vertBounds.maxV > 0) { // Let's not hash less than 272, we might use more later and have to rehash. 272 is very common. - nextTexture_->maxSeenV = std::max((u16)272, gstate_c.vertBounds.maxV); - } else if (gstate_c.vertBounds.maxV > nextTexture_->maxSeenV) { + entry->maxSeenV = std::max((u16)272, gstate_c.vertBounds.maxV); + } else if (gstate_c.vertBounds.maxV > entry->maxSeenV) { // The max height changed, so we're better off hashing the entire thing. - nextTexture_->maxSeenV = 512; - nextTexture_->status |= TexCacheEntry::STATUS_FREE_CHANGE; + entry->maxSeenV = 512; + entry->status |= TexCacheEntry::STATUS_FREE_CHANGE; } } else { // Otherwise, we need to reset to ensure we use the whole thing. // Can't tell how much is used. // TODO: We could tell for texcoord UV gen, and apply scale to max? - nextTexture_->maxSeenV = 512; + entry->maxSeenV = 512; } } } diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index bf8e11e7d6..8d8dfcd977 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -150,7 +150,7 @@ protected: u32 EstimateTexMemoryUsage(const TexCacheEntry *entry); void GetSamplingParams(int &minFilt, int &magFilt, bool &sClamp, bool &tClamp, float &lodBias, u8 maxLevel, u32 addr); - void UpdateMaxSeenV(bool throughMode); + void UpdateMaxSeenV(TexCacheEntry *entry, bool throughMode); virtual bool AttachFramebuffer(TexCacheEntry *entry, u32 address, VirtualFramebuffer *framebuffer, u32 texaddrOffset = 0) = 0; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index dae0ff03b8..af48f6ca7e 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -704,7 +704,7 @@ void TextureCacheDX9::ApplyTexture() { if (nextTexture_->framebuffer) { ApplyTextureFramebuffer(nextTexture_, nextTexture_->framebuffer); } else { - UpdateMaxSeenV(gstate.isModeThrough()); + UpdateMaxSeenV(nextTexture_, gstate.isModeThrough()); LPDIRECT3DTEXTURE9 texture = DxTex(nextTexture_); pD3Ddevice->SetTexture(0, texture); diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 09bc84957d..9099d87952 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -766,23 +766,27 @@ void TextureCache::SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffe } void TextureCache::ApplyTexture() { - if (nextTexture_ == nullptr) { + TexCacheEntry *const entry = nextTexture_; + if (entry == nullptr) { return; } - - if (nextTexture_->framebuffer) { - ApplyTextureFramebuffer(nextTexture_, nextTexture_->framebuffer); - } else { - UpdateMaxSeenV(gstate.isModeThrough()); - - if (nextTexture_->textureName != lastBoundTexture) { - glBindTexture(GL_TEXTURE_2D, nextTexture_->textureName); - lastBoundTexture = nextTexture_->textureName; - } - UpdateSamplingParams(*nextTexture_, false); - } - nextTexture_ = nullptr; + + entry->lastFrame = gpuStats.numFlips; + if (entry->framebuffer) { + ApplyTextureFramebuffer(entry, entry->framebuffer); + } else { + UpdateMaxSeenV(entry, gstate.isModeThrough()); + + if (entry->textureName != lastBoundTexture) { + glBindTexture(GL_TEXTURE_2D, entry->textureName); + lastBoundTexture = entry->textureName; + } + UpdateSamplingParams(*entry, false); + + gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; + gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; + } } void TextureCache::DownloadFramebufferForClut(u32 clutAddr, u32 bytes) { @@ -986,7 +990,6 @@ bool TextureCache::SetOffsetTexture(u32 offset) { if (success && entry->framebuffer) { // This will not apply the texture immediately. SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return true; } @@ -1084,6 +1087,7 @@ void TextureCache::SetTexture(bool force) { entry = &iter->second; // Validate the texture still matches the cache entry. bool match = entry->Matches(dim, format, maxLevel); + bool initialMatch = match; const char *reason = "different params"; // Check for FBO - slow! @@ -1094,7 +1098,6 @@ void TextureCache::SetTexture(bool force) { } SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return; } else { // Make sure we re-evaluate framebuffers. @@ -1217,46 +1220,19 @@ void TextureCache::SetTexture(bool force) { if (match) { // TODO: Mark the entry reliable if it's been safe for long enough? //got one! - entry->lastFrame = gpuStats.numFlips; if (entry->textureName != lastBoundTexture) { - gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; - gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; + gstate_c.curTextureWidth = w; + gstate_c.curTextureHeight = h; } nextTexture_ = entry; + nextNeedsRehash_ = false; + nextNeedsRebuild_= false; VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr); return; //Done! } else { - cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry); - entry->numInvalidated++; - gpuStats.numTextureInvalidations++; - DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", texaddr, reason); - if (doDelete) { - if (entry->maxLevel == maxLevel && entry->dim == gstate.getTextureDimension(0) && entry->format == format && standardScaleFactor_ == 1) { - // Actually, if size and number of levels match, let's try to avoid deleting and recreating. - // Instead, let's use glTexSubImage to replace the images. - replaceImages = true; - } else { - if (entry->textureName == lastBoundTexture) { - lastBoundTexture = INVALID_TEX; - } - glDeleteTextures(1, &entry->textureName); - } - } - // Clear the reliable bit if set. - if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); - } - - // Also, mark any textures with the same address but different clut. They need rechecking. - if (cluthash != 0) { - const u64 cachekeyMin = (u64)(texaddr & 0x3FFFFFFF) << 32; - const u64 cachekeyMax = cachekeyMin + (1ULL << 32); - for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) { - if (it->second.cluthash != cluthash) { - it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK; - } - } - } + entry->cluthash = cluthash; + nextTexture_ = entry; + HandleTextureChange(reason, initialMatch, doDelete); } } else { VERBOSE_LOG(G3D, "No texture in cache, decoding..."); @@ -1275,36 +1251,74 @@ void TextureCache::SetTexture(bool force) { } } - if ((bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && texaddr >= PSP_GetKernelMemoryEnd()) { - ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff); - // Proceeding here can cause a crash. - return; - } - // We have to decode it, let's setup the cache entry first. entry->addr = texaddr; entry->hash = texhash; + entry->dim = dim; entry->format = format; - entry->lastFrame = gpuStats.numFlips; - entry->framebuffer = 0; entry->maxLevel = maxLevel; - entry->lodBias = 0.0f; - - entry->dim = gstate.getTextureDimension(0); - entry->bufw = bufw; // This would overestimate the size in many case so we underestimate instead // to avoid excessive clearing caused by cache invalidations. entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; + entry->bufw = bufw; entry->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash; entry->cluthash = cluthash; - entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; - gstate_c.curTextureWidth = w; gstate_c.curTextureHeight = h; + nextTexture_ = entry; + nextNeedsRehash_ = true; + nextNeedsRebuild_= true; + BuildTexture(replaceImages); +} + +bool TextureCache::HandleTextureChange(const char *reason, bool initialMatch, bool doDelete) { + TexCacheEntry *const entry = nextTexture_; + bool replaceImages = false; + + cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry); + entry->numInvalidated++; + gpuStats.numTextureInvalidations++; + DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason); + if (doDelete) { + if (initialMatch && standardScaleFactor_ == 1) { + // Actually, if size and number of levels match, let's try to avoid deleting and recreating. + // Instead, let's use glTexSubImage to replace the images. + replaceImages = true; + } else { + if (entry->textureName == lastBoundTexture) { + lastBoundTexture = INVALID_TEX; + } + glDeleteTextures(1, &entry->textureName); + } + } + // Clear the reliable bit if set. + if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { + entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); + } + + // Also, mark any textures with the same address but different clut. They need rechecking. + if (entry->cluthash != 0) { + const u64 cachekeyMin = (u64)(entry->addr & 0x3FFFFFFF) << 32; + const u64 cachekeyMax = cachekeyMin + (1ULL << 32); + for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) { + if (it->second.cluthash != entry->cluthash) { + it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK; + } + } + } + + return replaceImages; +} + +void TextureCache::BuildTexture(bool replaceImages) { + TexCacheEntry *const entry = nextTexture_; + + entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; + // For the estimate, we assume cluts always point to 8888 for simplicity. cacheSizeEstimate_ += EstimateTexMemoryUsage(entry); @@ -1314,6 +1328,7 @@ void TextureCache::SetTexture(bool force) { } // Before we go reading the texture from memory, let's check for render-to-texture. + entry->framebuffer = 0; for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { auto framebuffer = fbCache_[i]; AttachFramebuffer(entry, framebuffer->fb_address, framebuffer); @@ -1322,15 +1337,22 @@ void TextureCache::SetTexture(bool force) { // If we ended up with a framebuffer, attach it - no texture decoding needed. if (entry->framebuffer) { SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return; } + + if ((entry->bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && entry->addr >= PSP_GetKernelMemoryEnd()) { + ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff); + // Proceeding here can cause a crash. + return; + } + glBindTexture(GL_TEXTURE_2D, entry->textureName); lastBoundTexture = entry->textureName; // Adjust maxLevel to actually present levels.. bool badMipSizes = false; - for (u32 i = 0; i <= maxLevel; i++) { + int maxLevel = entry->maxLevel; + for (int i = 0; i <= maxLevel; i++) { // If encountering levels pointing to nothing, adjust max level. u32 levelTexaddr = gstate.getTextureAddress(i); if (!Memory::IsValidAddress(levelTexaddr)) { @@ -1354,7 +1376,7 @@ void TextureCache::SetTexture(bool force) { } // If GLES3 is available, we can preallocate the storage, which makes texture loading more efficient. - GLenum dstFmt = GetDestFormat(format, gstate.getClutPaletteFormat()); + GLenum dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); int scaleFactor = standardScaleFactor_; @@ -1364,6 +1386,9 @@ void TextureCache::SetTexture(bool force) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } + u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0; + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h); if (replaced.GetSize(0, w, h)) { // We're replacing, so we won't scale. @@ -1459,9 +1484,6 @@ void TextureCache::SetTexture(bool force) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropyLevel); } - gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; - gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; - // This will rebind it, but that's okay. nextTexture_ = entry; UpdateSamplingParams(*entry, true); diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index 10cddc6dcb..2ec2529e1a 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -107,6 +107,9 @@ private: void SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); + bool HandleTextureChange(const char *reason, bool initialMatch, bool doDelete); + void BuildTexture(bool replaceImages); + std::vector nameCache_; TexCache secondCache; u32 secondCacheSizeEstimate_; @@ -133,6 +136,9 @@ private: DepalShaderCache *depalShaderCache_; ShaderManager *shaderManager_; DrawEngineGLES *transformDraw_; + + bool nextNeedsRehash_; + bool nextNeedsRebuild_; }; GLenum getClutDestFormat(GEPaletteFormat format); From 82c1eaad65d2347eb000b03ee87c196f707a6220 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 13:03:40 -0700 Subject: [PATCH 02/10] Let's not overuse nextTexture_. --- GPU/GLES/TextureCache.cpp | 12 ++++-------- GPU/GLES/TextureCache.h | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 9099d87952..34345f20a1 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -1231,8 +1231,7 @@ void TextureCache::SetTexture(bool force) { return; //Done! } else { entry->cluthash = cluthash; - nextTexture_ = entry; - HandleTextureChange(reason, initialMatch, doDelete); + HandleTextureChange(entry, reason, initialMatch, doDelete); } } else { VERBOSE_LOG(G3D, "No texture in cache, decoding..."); @@ -1272,11 +1271,10 @@ void TextureCache::SetTexture(bool force) { nextTexture_ = entry; nextNeedsRehash_ = true; nextNeedsRebuild_= true; - BuildTexture(replaceImages); + BuildTexture(entry, replaceImages); } -bool TextureCache::HandleTextureChange(const char *reason, bool initialMatch, bool doDelete) { - TexCacheEntry *const entry = nextTexture_; +bool TextureCache::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) { bool replaceImages = false; cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry); @@ -1314,9 +1312,7 @@ bool TextureCache::HandleTextureChange(const char *reason, bool initialMatch, bo return replaceImages; } -void TextureCache::BuildTexture(bool replaceImages) { - TexCacheEntry *const entry = nextTexture_; - +void TextureCache::BuildTexture(TexCacheEntry *const entry, bool replaceImages) { entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; // For the estimate, we assume cluts always point to 8888 for simplicity. diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index 2ec2529e1a..0a5b10e867 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -107,8 +107,8 @@ private: void SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); - bool HandleTextureChange(const char *reason, bool initialMatch, bool doDelete); - void BuildTexture(bool replaceImages); + bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete); + void BuildTexture(TexCacheEntry *const entry, bool replaceImages); std::vector nameCache_; TexCache secondCache; From bce5c74dbfa60f060bcf27e08a09e64ed8a30015 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 13:11:35 -0700 Subject: [PATCH 03/10] Refactor out texture rehash check. --- GPU/GLES/TextureCache.cpp | 130 ++++++++++++++++++++++---------------- GPU/GLES/TextureCache.h | 1 + 2 files changed, 75 insertions(+), 56 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 34345f20a1..b8670a66f2 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -1145,67 +1145,22 @@ void TextureCache::SetTexture(bool force) { rehash = true; } - bool hashFail = false; if (texhash != entry->hash) { - fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); - hashFail = true; + match = false; + } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { rehash = false; } - if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) { - fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); - if (fullhash != entry->fullhash) { - hashFail = true; - } else { - if (g_Config.bTextureBackoffCache) { - if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) { - // Reset to STATUS_HASHING. - entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); - entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) { - entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } - } - - if (hashFail) { - match = false; - reason = "hash fail"; - entry->status |= TexCacheEntry::STATUS_UNRELIABLE; - if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) { - if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) { - entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE; - } else { - entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } - 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 (g_Config.bTextureSecondaryCache) { - 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); - secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry); - secondCache[secondKey] = *entry; - doDelete = false; - } - } + if (rehash) { + if (!CheckFullHash(entry, doDelete)) { + match = false; + } else if (nextTexture_ != nullptr) { + // Secondary cache picked a different texture, use it. + entry = nextTexture_; + nextTexture_ = nullptr; } + // CheckFullHash updated the entry's fullhash value. + fullhash = entry->fullhash; } } @@ -1274,6 +1229,69 @@ void TextureCache::SetTexture(bool force) { BuildTexture(entry, replaceImages); } +bool TextureCache::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) { + bool hashFail = false; + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); + u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + if (fullhash != entry->fullhash) { + hashFail = true; + } else { + if (g_Config.bTextureBackoffCache) { + if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) { + // Reset to STATUS_HASHING. + entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); + entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) { + entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } + + if (hashFail) { + entry->status |= TexCacheEntry::STATUS_UNRELIABLE; + if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) { + if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) { + entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE; + } else { + entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } + 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 (g_Config.bTextureSecondaryCache) { + if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) { + u64 secondKey = fullhash | (u64)entry->cluthash << 32; + TexCache::iterator secondIter = secondCache.find(secondKey); + if (secondIter != secondCache.end()) { + TexCacheEntry *secondEntry = &secondIter->second; + if (secondEntry->Matches(entry->dim, entry->format, entry->maxLevel)) { + // Reset the numInvalidated value lower, we got a match. + if (entry->numInvalidated > 8) { + --entry->numInvalidated; + } + nextTexture_ = secondEntry; + return true; + } + } else { + secondKey = entry->fullhash | ((u64)entry->cluthash << 32); + secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry); + secondCache[secondKey] = *entry; + doDelete = false; + } + } + } + + // We know it failed, so update the full hash right away. + entry->fullhash = fullhash; + return false; + } + + return true; +} + bool TextureCache::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) { bool replaceImages = false; diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index 0a5b10e867..e037f36f7e 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -107,6 +107,7 @@ private: void SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); + bool CheckFullHash(TexCacheEntry *const entry, bool &doDelete); bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete); void BuildTexture(TexCacheEntry *const entry, bool replaceImages); From fbef79826b00a462a1cd9297c6842ace6a7bb36d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 13:22:53 -0700 Subject: [PATCH 04/10] Oops, correct replaceImages. --- GPU/GLES/TextureCache.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index b8670a66f2..294635b92d 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -766,7 +766,7 @@ void TextureCache::SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffe } void TextureCache::ApplyTexture() { - TexCacheEntry *const entry = nextTexture_; + TexCacheEntry *entry = nextTexture_; if (entry == nullptr) { return; } @@ -1179,6 +1179,13 @@ void TextureCache::SetTexture(bool force) { gstate_c.curTextureWidth = w; gstate_c.curTextureHeight = h; } + if (rehash) { + // Update in case any of these changed. + entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; + entry->bufw = bufw; + entry->cluthash = cluthash; + } + nextTexture_ = entry; nextNeedsRehash_ = false; nextNeedsRebuild_= false; @@ -1186,7 +1193,7 @@ void TextureCache::SetTexture(bool force) { return; //Done! } else { entry->cluthash = cluthash; - HandleTextureChange(entry, reason, initialMatch, doDelete); + replaceImages = HandleTextureChange(entry, reason, initialMatch, doDelete); } } else { VERBOSE_LOG(G3D, "No texture in cache, decoding..."); From 6bd86f462d1f90c3e65d1e64dfb777248ce4c09c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 13:24:53 -0700 Subject: [PATCH 05/10] Move texture hashing to end of texture apply. This might also make it more possible to do it on a thread, but it's likely still too quick. --- GPU/GLES/TextureCache.cpp | 60 ++++++++++++++++++++++++++------------- GPU/GLES/TextureCache.h | 2 ++ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 294635b92d..2e8cca2e69 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -772,6 +772,38 @@ void TextureCache::ApplyTexture() { } nextTexture_ = nullptr; + bool replaceImages = false; + if (nextNeedsRebuild_) { + if (nextNeedsRehash_) { + // Update the hash on the texture. + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); + u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + } + if (nextNeedsChange_) { + // This texture existed previously, let's handle the change. + replaceImages = HandleTextureChange(entry, nextChangeReason_, false, true); + } + + // We actually build afterward (shared with rehash rebuild.) + } else if (nextNeedsRehash_) { + // Okay, this matched and didn't change - but let's check the hash. Maybe it will change. + bool doDelete = true; + if (!CheckFullHash(entry, doDelete)) { + replaceImages = HandleTextureChange(entry, "hash fail", true, doDelete); + nextNeedsRebuild_ = true; + } else if (nextTexture_ != nullptr) { + // Secondary cache picked a different texture, use it. + entry = nextTexture_; + nextTexture_ = nullptr; + } + } + + // Okay, now actually rebuild the texture if needed. + if (nextNeedsRebuild_) { + BuildTexture(entry, replaceImages); + } + entry->lastFrame = gpuStats.numFlips; if (entry->framebuffer) { ApplyTextureFramebuffer(entry, entry->framebuffer); @@ -1075,19 +1107,16 @@ void TextureCache::SetTexture(bool force) { u8 maxLevel = gstate.getTextureMaxLevel(); u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr)); - u32 fullhash = 0; TexCache::iterator iter = cache.find(cachekey); TexCacheEntry *entry = NULL; gstate_c.needShaderTexClamp = false; gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE; - bool replaceImages = false; if (iter != cache.end()) { entry = &iter->second; // Validate the texture still matches the cache entry. bool match = entry->Matches(dim, format, maxLevel); - bool initialMatch = match; const char *reason = "different params"; // Check for FBO - slow! @@ -1108,7 +1137,6 @@ void TextureCache::SetTexture(bool force) { } bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE; - bool doDelete = true; // First let's see if another texture with the same address had a hashfail. if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) { @@ -1150,18 +1178,6 @@ void TextureCache::SetTexture(bool force) { } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { rehash = false; } - - if (rehash) { - if (!CheckFullHash(entry, doDelete)) { - match = false; - } else if (nextTexture_ != nullptr) { - // Secondary cache picked a different texture, use it. - entry = nextTexture_; - nextTexture_ = nullptr; - } - // CheckFullHash updated the entry's fullhash value. - fullhash = entry->fullhash; - } } if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) { @@ -1187,13 +1203,17 @@ void TextureCache::SetTexture(bool force) { } nextTexture_ = entry; - nextNeedsRehash_ = false; + nextNeedsRehash_ = rehash; + nextNeedsChange_ = false; + // Might need a rebuild if the hash fails. nextNeedsRebuild_= false; VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr); return; //Done! } else { entry->cluthash = cluthash; - replaceImages = HandleTextureChange(entry, reason, initialMatch, doDelete); + nextTexture_ = entry; + nextChangeReason_ = reason; + nextNeedsChange_ = true; } } else { VERBOSE_LOG(G3D, "No texture in cache, decoding..."); @@ -1210,6 +1230,8 @@ void TextureCache::SetTexture(bool force) { } else { entry->status = TexCacheEntry::STATUS_UNRELIABLE; } + + nextNeedsChange_ = false; } // We have to decode it, let's setup the cache entry first. @@ -1224,7 +1246,6 @@ void TextureCache::SetTexture(bool force) { entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; entry->bufw = bufw; - entry->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash; entry->cluthash = cluthash; gstate_c.curTextureWidth = w; @@ -1233,7 +1254,6 @@ void TextureCache::SetTexture(bool force) { nextTexture_ = entry; nextNeedsRehash_ = true; nextNeedsRebuild_= true; - BuildTexture(entry, replaceImages); } bool TextureCache::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) { diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index e037f36f7e..5f2e632fae 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -138,7 +138,9 @@ private: ShaderManager *shaderManager_; DrawEngineGLES *transformDraw_; + const char *nextChangeReason_; bool nextNeedsRehash_; + bool nextNeedsChange_; bool nextNeedsRebuild_; }; From bfe23361cdcda391d61bb0d986c872e94f042e7d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 12:57:17 -0700 Subject: [PATCH 06/10] Update maxSeenV earlier. Now, the initial fullhash is already on the correct range. --- GPU/GLES/TextureCache.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 2e8cca2e69..fcf68fc647 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -772,6 +772,8 @@ void TextureCache::ApplyTexture() { } nextTexture_ = nullptr; + UpdateMaxSeenV(entry, gstate.isModeThrough()); + bool replaceImages = false; if (nextNeedsRebuild_) { if (nextNeedsRehash_) { @@ -796,6 +798,7 @@ void TextureCache::ApplyTexture() { // Secondary cache picked a different texture, use it. entry = nextTexture_; nextTexture_ = nullptr; + UpdateMaxSeenV(entry, gstate.isModeThrough()); } } @@ -808,8 +811,6 @@ void TextureCache::ApplyTexture() { if (entry->framebuffer) { ApplyTextureFramebuffer(entry, entry->framebuffer); } else { - UpdateMaxSeenV(entry, gstate.isModeThrough()); - if (entry->textureName != lastBoundTexture) { glBindTexture(GL_TEXTURE_2D, entry->textureName); lastBoundTexture = entry->textureName; @@ -1210,8 +1211,6 @@ void TextureCache::SetTexture(bool force) { VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr); return; //Done! } else { - entry->cluthash = cluthash; - nextTexture_ = entry; nextChangeReason_ = reason; nextNeedsChange_ = true; } From 4ce02e0920646fa38f5ab3adf40fc74963141126 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 13:42:06 -0700 Subject: [PATCH 07/10] Fix a couple more bugs in texture build. --- GPU/GLES/TextureCache.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index fcf68fc647..32ec11020b 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -780,7 +780,7 @@ void TextureCache::ApplyTexture() { // Update the hash on the texture. int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); - u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + entry->fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); } if (nextNeedsChange_) { // This texture existed previously, let's handle the change. @@ -1525,7 +1525,6 @@ void TextureCache::BuildTexture(TexCacheEntry *const entry, bool replaceImages) } // This will rebind it, but that's okay. - nextTexture_ = entry; UpdateSamplingParams(*entry, true); //glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); From f1dda6a53d3d7fc8d01038e2ce4bd5d3959906ce Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 16:20:05 -0700 Subject: [PATCH 08/10] d3d9: Move texture hashing after vertex decode. --- GPU/Directx9/TextureCacheDX9.cpp | 332 ++++++++++++++++++------------- GPU/Directx9/TextureCacheDX9.h | 9 + 2 files changed, 206 insertions(+), 135 deletions(-) diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index af48f6ca7e..5074418d34 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -697,22 +697,61 @@ void TextureCacheDX9::SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebu } void TextureCacheDX9::ApplyTexture() { - if (nextTexture_ == nullptr) { + TexCacheEntry *entry = nextTexture_; + if (entry == nullptr) { return; } + nextTexture_ = nullptr; - if (nextTexture_->framebuffer) { - ApplyTextureFramebuffer(nextTexture_, nextTexture_->framebuffer); - } else { - UpdateMaxSeenV(nextTexture_, gstate.isModeThrough()); + UpdateMaxSeenV(entry, gstate.isModeThrough()); - LPDIRECT3DTEXTURE9 texture = DxTex(nextTexture_); - pD3Ddevice->SetTexture(0, texture); - lastBoundTexture = texture; - UpdateSamplingParams(*nextTexture_, false); + bool replaceImages = false; + if (nextNeedsRebuild_) { + if (nextNeedsRehash_) { + // Update the hash on the texture. + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); + entry->fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + } + if (nextNeedsChange_) { + // This texture existed previously, let's handle the change. + replaceImages = HandleTextureChange(entry, nextChangeReason_, false, true); + } + + // We actually build afterward (shared with rehash rebuild.) + } else if (nextNeedsRehash_) { + // Okay, this matched and didn't change - but let's check the hash. Maybe it will change. + bool doDelete = true; + if (!CheckFullHash(entry, doDelete)) { + replaceImages = HandleTextureChange(entry, "hash fail", true, doDelete); + nextNeedsRebuild_ = true; + } else if (nextTexture_ != nullptr) { + // Secondary cache picked a different texture, use it. + entry = nextTexture_; + nextTexture_ = nullptr; + UpdateMaxSeenV(entry, gstate.isModeThrough()); + } } - nextTexture_ = nullptr; + // Okay, now actually rebuild the texture if needed. + if (nextNeedsRebuild_) { + BuildTexture(entry, replaceImages); + } + + entry->lastFrame = gpuStats.numFlips; + if (entry->framebuffer) { + ApplyTextureFramebuffer(entry, entry->framebuffer); + } else { + LPDIRECT3DTEXTURE9 texture = DxTex(entry); + if (texture != lastBoundTexture) { + pD3Ddevice->SetTexture(0, texture); + lastBoundTexture = texture; + } + UpdateSamplingParams(*entry, false); + + gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; + gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; + } } void TextureCacheDX9::DownloadFramebufferForClut(u32 clutAddr, u32 bytes) { @@ -909,7 +948,6 @@ bool TextureCacheDX9::SetOffsetTexture(u32 offset) { if (success && entry->framebuffer) { SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return true; } @@ -966,7 +1004,6 @@ void TextureCacheDX9::SetTexture(bool force) { u8 maxLevel = gstate.getTextureMaxLevel(); u32 texhash = MiniHash((const u32 *)Memory::GetPointer(texaddr)); - u32 fullhash = 0; TexCache::iterator iter = cache.find(cachekey); TexCacheEntry *entry = NULL; @@ -974,7 +1011,6 @@ void TextureCacheDX9::SetTexture(bool force) { gstate_c.bgraTexture = true; gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE; bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; - bool replaceImages = false; if (iter != cache.end()) { entry = &iter->second; @@ -990,7 +1026,6 @@ void TextureCacheDX9::SetTexture(bool force) { } SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return; } else { // Make sure we re-evaluate framebuffers. @@ -1001,7 +1036,6 @@ void TextureCacheDX9::SetTexture(bool force) { } bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE; - bool doDelete = true; // First let's see if another texture with the same address had a hashfail. if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) { @@ -1038,67 +1072,10 @@ void TextureCacheDX9::SetTexture(bool force) { rehash = true; } - bool hashFail = false; if (texhash != entry->hash) { - fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); - hashFail = true; - rehash = false; - } - - if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) { - fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); - if (fullhash != entry->fullhash) { - hashFail = true; - } else { - if (g_Config.bTextureBackoffCache) { - if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) { - // Reset to STATUS_HASHING. - entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); - entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) { - entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } - } - - if (hashFail) { match = false; - reason = "hash fail"; - entry->status |= TexCacheEntry::STATUS_UNRELIABLE; - if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) { - if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) { - entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE; - } else { - entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } - 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 (g_Config.bTextureSecondaryCache) { - 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); - secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry); - secondCache[secondKey] = *entry; - doDelete = false; - } - } - } + } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { + rehash = false; } } @@ -1113,48 +1090,27 @@ void TextureCacheDX9::SetTexture(bool force) { if (match) { // TODO: Mark the entry reliable if it's been safe for long enough? //got one! - entry->lastFrame = gpuStats.numFlips; - LPDIRECT3DTEXTURE9 texture = DxTex(entry); - if (texture != lastBoundTexture) { - nextTexture_ = entry; - gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; - gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; + if (entry->texturePtr != lastBoundTexture) { + gstate_c.curTextureWidth = w; + gstate_c.curTextureHeight = h; } - UpdateSamplingParams(*entry, false); + if (rehash) { + // Update in case any of these changed. + entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; + entry->bufw = bufw; + entry->cluthash = cluthash; + } + + nextTexture_ = entry; + nextNeedsRehash_ = rehash; + nextNeedsChange_ = false; + // Might need a rebuild if the hash fails. + nextNeedsRebuild_= false; VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr); return; //Done! } else { - cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry); - entry->numInvalidated++; - gpuStats.numTextureInvalidations++; - DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", texaddr, reason); - if (doDelete) { - if (entry->maxLevel == maxLevel && entry->dim == gstate.getTextureDimension(0) && entry->format == format && standardScaleFactor_ == 1) { - // Actually, if size and number of levels match, let's try to avoid deleting and recreating. - // Instead, let's use glTexSubImage to replace the images. - replaceImages = true; - } else { - if (entry->texturePtr == lastBoundTexture) { - lastBoundTexture = INVALID_TEX; - } - ReleaseTexture(entry); - } - } - // Clear the reliable bit if set. - if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); - } - - // Also, mark any textures with the same address but different clut. They need rechecking. - if (cluthash != 0) { - const u64 cachekeyMin = (u64)(texaddr & 0x3FFFFFFF) << 32; - const u64 cachekeyMax = cachekeyMin + (1ULL << 32); - for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) { - if (it->second.cluthash != cluthash) { - it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK; - } - } - } + nextChangeReason_ = reason; + nextNeedsChange_ = true; } } else { VERBOSE_LOG(G3D, "No texture in cache, decoding..."); @@ -1171,36 +1127,136 @@ void TextureCacheDX9::SetTexture(bool force) { } else { entry->status = TexCacheEntry::STATUS_UNRELIABLE; } - } - if ((bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && texaddr >= PSP_GetKernelMemoryEnd()) { - ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff); + nextNeedsChange_ = false; } // We have to decode it, let's setup the cache entry first. entry->addr = texaddr; entry->hash = texhash; - entry->format = format; - entry->lastFrame = gpuStats.numFlips; - entry->framebuffer = 0; - entry->maxLevel = maxLevel; - entry->lodBias = 0.0f; - entry->dim = dim; - entry->bufw = bufw; + entry->format = format; + entry->maxLevel = maxLevel; // This would overestimate the size in many case so we underestimate instead // to avoid excessive clearing caused by cache invalidations. entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; + entry->bufw = bufw; - entry->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash; entry->cluthash = cluthash; - entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; - gstate_c.curTextureWidth = w; gstate_c.curTextureHeight = h; + nextTexture_ = entry; + nextNeedsRehash_ = true; + nextNeedsRebuild_= true; +} + +bool TextureCacheDX9::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) { + bool hashFail = false; + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); + u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + if (fullhash != entry->fullhash) { + hashFail = true; + } else { + if (g_Config.bTextureBackoffCache) { + if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) { + // Reset to STATUS_HASHING. + entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); + entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) { + entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } + + if (hashFail) { + entry->status |= TexCacheEntry::STATUS_UNRELIABLE; + if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) { + if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) { + entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE; + } else { + entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } + 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 (g_Config.bTextureSecondaryCache) { + if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) { + u64 secondKey = fullhash | (u64)entry->cluthash << 32; + TexCache::iterator secondIter = secondCache.find(secondKey); + if (secondIter != secondCache.end()) { + TexCacheEntry *secondEntry = &secondIter->second; + if (secondEntry->Matches(entry->dim, entry->format, entry->maxLevel)) { + // Reset the numInvalidated value lower, we got a match. + if (entry->numInvalidated > 8) { + --entry->numInvalidated; + } + nextTexture_ = secondEntry; + return true; + } + } else { + secondKey = entry->fullhash | ((u64)entry->cluthash << 32); + secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry); + secondCache[secondKey] = *entry; + doDelete = false; + } + } + } + + // We know it failed, so update the full hash right away. + entry->fullhash = fullhash; + return false; + } + + return true; +} + +bool TextureCacheDX9::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) { + bool replaceImages = false; + + cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry); + entry->numInvalidated++; + gpuStats.numTextureInvalidations++; + DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason); + if (doDelete) { + if (initialMatch && standardScaleFactor_ == 1) { + // Actually, if size and number of levels match, let's try to avoid deleting and recreating. + // Instead, let's use glTexSubImage to replace the images. + replaceImages = true; + } else { + if (entry->texturePtr == lastBoundTexture) { + lastBoundTexture = INVALID_TEX; + } + ReleaseTexture(entry); + } + } + // Clear the reliable bit if set. + if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { + entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); + } + + // Also, mark any textures with the same address but different clut. They need rechecking. + if (entry->cluthash != 0) { + const u64 cachekeyMin = (u64)(entry->addr & 0x3FFFFFFF) << 32; + const u64 cachekeyMax = cachekeyMin + (1ULL << 32); + for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) { + if (it->second.cluthash != entry->cluthash) { + it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK; + } + } + } + + return replaceImages; +} + +void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry, bool replaceImages) { + entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; + // For the estimate, we assume cluts always point to 8888 for simplicity. cacheSizeEstimate_ += EstimateTexMemoryUsage(entry); @@ -1208,6 +1264,7 @@ void TextureCacheDX9::SetTexture(bool force) { // Should just always create one here or something (like GLES.) // Before we go reading the texture from memory, let's check for render-to-texture. + entry->framebuffer = 0; for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { auto framebuffer = fbCache_[i]; AttachFramebuffer(entry, framebuffer->fb_address, framebuffer); @@ -1216,13 +1273,19 @@ void TextureCacheDX9::SetTexture(bool force) { // If we ended up with a framebuffer, attach it - no texture decoding needed. if (entry->framebuffer) { SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; + return; + } + + if ((entry->bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && entry->addr >= PSP_GetKernelMemoryEnd()) { + ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff); + // Proceeding here can cause a crash. return; } // Adjust maxLevel to actually present levels.. bool badMipSizes = false; - for (u32 i = 0; i <= maxLevel; i++) { + int maxLevel = entry->maxLevel; + for (int i = 0; i <= maxLevel; i++) { // If encountering levels pointing to nothing, adjust max level. u32 levelTexaddr = gstate.getTextureAddress(i); if (!Memory::IsValidAddress(levelTexaddr)) { @@ -1230,7 +1293,7 @@ void TextureCacheDX9::SetTexture(bool force) { break; } - if (i > 0) { + if (i > 0 && gstate_c.Supports(GPU_SUPPORTS_TEXTURE_LOD_CONTROL)) { int tw = gstate.getTextureWidth(i); int th = gstate.getTextureHeight(i); if (tw != 1 && tw != (gstate.getTextureWidth(i - 1) >> 1)) @@ -1246,7 +1309,7 @@ void TextureCacheDX9::SetTexture(bool force) { } // If GLES3 is available, we can preallocate the storage, which makes texture loading more efficient. - D3DFORMAT dstFmt = GetDestFormat(format, gstate.getClutPaletteFormat()); + D3DFORMAT dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); int scaleFactor = standardScaleFactor_; @@ -1256,6 +1319,9 @@ void TextureCacheDX9::SetTexture(bool force) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } + u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0; + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h); if (replaced.GetSize(0, w, h)) { // We're replacing, so we won't scale. @@ -1302,7 +1368,7 @@ void TextureCacheDX9::SetTexture(bool force) { // Mipmapping is only enabled when texture scaling is disabled. if (maxLevel > 0 && scaleFactor == 1) { - for (u32 i = 1; i <= maxLevel; i++) { + for (int i = 1; i <= maxLevel; i++) { LoadTextureLevel(*entry, replaced, i, maxLevel, replaceImages, scaleFactor, dstFmt); } } @@ -1311,11 +1377,7 @@ void TextureCacheDX9::SetTexture(bool force) { entry->SetAlphaStatus(TexCacheEntry::Status(replaced.AlphaStatus())); } - gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; - gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; - - nextTexture_ = entry; - UpdateSamplingParams(*nextTexture_, true); + UpdateSamplingParams(*entry, true); } D3DFORMAT TextureCacheDX9::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const { diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index ff9db0fa20..c5d1ca4e6d 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -92,6 +92,10 @@ private: void SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); + bool CheckFullHash(TexCacheEntry *const entry, bool &doDelete); + bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete); + void BuildTexture(TexCacheEntry *const entry, bool replaceImages); + LPDIRECT3DTEXTURE9 &DxTex(TexCacheEntry *entry) { return *(LPDIRECT3DTEXTURE9 *)&entry->texturePtr; } @@ -126,6 +130,11 @@ private: FramebufferManagerDX9 *framebufferManager_; DepalShaderCacheDX9 *depalShaderCache_; ShaderManagerDX9 *shaderManager_; + + const char *nextChangeReason_; + bool nextNeedsRehash_; + bool nextNeedsChange_; + bool nextNeedsRebuild_; }; D3DFORMAT getClutDestFormat(GEPaletteFormat format); From ffef628667e656fd9d48fc6e8fb2e9a04cd3eb86 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 16:25:09 -0700 Subject: [PATCH 09/10] Vulkan: Move ApplyTexture after vert decode. --- GPU/Vulkan/DrawEngineVulkan.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index e2e1f0911f..410861a168 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -602,19 +602,16 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) { FrameData *frame = &frame_[curFrame_ & 1]; + bool textureNeedsApply = false; if (gstate_c.textureChanged != TEXCHANGE_UNCHANGED && !gstate.isModeClear() && gstate.isTextureMapEnabled()) { textureCache_->SetTexture(frame->pushUBO); + textureNeedsApply = true; gstate_c.textureChanged = TEXCHANGE_UNCHANGED; if (gstate_c.needShaderTexClamp) { // We will rarely need to set this, so let's do it every time on use rather than in runloop. // Most of the time non-framebuffer textures will be used which can be clamped themselves. shaderManager_->DirtyUniform(DIRTY_TEXCLAMP); } - textureCache_->ApplyTexture(imageView, sampler); - if (imageView == VK_NULL_HANDLE) - imageView = nullTexture_->GetImageView(); - if (sampler == VK_NULL_HANDLE) - sampler = nullSampler_; } GEPrimitiveType prim = prevPrim_; @@ -651,6 +648,14 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) { gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && ((hasColor && (gstate.materialupdate & 1)) || gstate.getMaterialAmbientA() == 255) && (!gstate.isLightingEnabled() || gstate.getAmbientA() == 255); } + if (textureNeedsApply) { + textureCache_->ApplyTexture(imageView, sampler); + if (imageView == VK_NULL_HANDLE) + imageView = nullTexture_->GetImageView(); + if (sampler == VK_NULL_HANDLE) + sampler = nullSampler_; + } + VulkanPipelineRasterStateKey pipelineKey; VulkanDynamicState dynState; ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey, dynState); @@ -743,6 +748,14 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) { // Only here, where we know whether to clear or to draw primitives, should we actually set the current framebuffer! Because that gives use the opportunity // to use a "pre-clear" render pass, for high efficiency on tilers. if (result.action == SW_DRAW_PRIMITIVES) { + if (textureNeedsApply) { + textureCache_->ApplyTexture(imageView, sampler); + if (imageView == VK_NULL_HANDLE) + imageView = nullTexture_->GetImageView(); + if (sampler == VK_NULL_HANDLE) + sampler = nullSampler_; + } + VulkanPipelineRasterStateKey pipelineKey; VulkanDynamicState dynState; ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey, dynState); From 53535558f8780a82fd0f6d4fe6b23975fff55206 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 1 May 2016 17:27:14 -0700 Subject: [PATCH 10/10] Vulkan: Move texture hash/decode after vtx decode. --- GPU/Vulkan/DrawEngineVulkan.cpp | 8 +- GPU/Vulkan/TextureCacheVulkan.cpp | 339 +++++++++++++++++------------- GPU/Vulkan/TextureCacheVulkan.h | 13 +- 3 files changed, 203 insertions(+), 157 deletions(-) diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 410861a168..558628d93f 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -581,8 +581,6 @@ void DrawEngineVulkan::DirtyAllUBOs() { if (!gstate.isModeClear()) { // TODO: Test texture? - textureCache_->ApplyTexture(); - if (fboTexNeedBind_) { // Note that this is positions, not UVs, that we need the copy from. framebufferManager_->BindFramebufferColor(1, nullptr, BINDFBCOLOR_MAY_COPY); @@ -604,7 +602,7 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) { bool textureNeedsApply = false; if (gstate_c.textureChanged != TEXCHANGE_UNCHANGED && !gstate.isModeClear() && gstate.isTextureMapEnabled()) { - textureCache_->SetTexture(frame->pushUBO); + textureCache_->SetTexture(); textureNeedsApply = true; gstate_c.textureChanged = TEXCHANGE_UNCHANGED; if (gstate_c.needShaderTexClamp) { @@ -649,7 +647,7 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) { } if (textureNeedsApply) { - textureCache_->ApplyTexture(imageView, sampler); + textureCache_->ApplyTexture(frame->pushUBO, imageView, sampler); if (imageView == VK_NULL_HANDLE) imageView = nullTexture_->GetImageView(); if (sampler == VK_NULL_HANDLE) @@ -749,7 +747,7 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) { // to use a "pre-clear" render pass, for high efficiency on tilers. if (result.action == SW_DRAW_PRIMITIVES) { if (textureNeedsApply) { - textureCache_->ApplyTexture(imageView, sampler); + textureCache_->ApplyTexture(frame->pushUBO, imageView, sampler); if (imageView == VK_NULL_HANDLE) imageView = nullTexture_->GetImageView(); if (sampler == VK_NULL_HANDLE) diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index ca20481862..b93feaa9e5 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -699,50 +699,68 @@ void TextureCacheVulkan::SetTextureFramebuffer(TexCacheEntry *entry, VirtualFram } } -void TextureCacheVulkan::ApplyTexture(VkImageView &imageView, VkSampler &sampler) { - if (nextTexture_ == nullptr) { +void TextureCacheVulkan::ApplyTexture(VulkanPushBuffer *uploadBuffer, VkImageView &imageView, VkSampler &sampler) { + TexCacheEntry *entry = nextTexture_; + if (entry == nullptr) { imageView = VK_NULL_HANDLE; sampler = VK_NULL_HANDLE; return; } - VkCommandBuffer cmd = nullptr; - if (nextTexture_->framebuffer) { - ApplyTextureFramebuffer(cmd, nextTexture_, nextTexture_->framebuffer, imageView, sampler); - } else if (nextTexture_->vkTex) { - // If the texture is >= 512 pixels tall... - if (nextTexture_->dim >= 0x900) { - // Texture scale/offset and gen modes don't apply in through. - // So we can optimize how much of the texture we look at. - if (gstate.isModeThrough()) { - if (nextTexture_->maxSeenV == 0 && gstate_c.vertBounds.maxV > 0) { - // Let's not hash less than 272, we might use more later and have to rehash. 272 is very common. - nextTexture_->maxSeenV = std::max((u16)272, gstate_c.vertBounds.maxV); - } else if (gstate_c.vertBounds.maxV > nextTexture_->maxSeenV) { - // The max height changed, so we're better off hashing the entire thing. - nextTexture_->maxSeenV = 512; - nextTexture_->status |= TexCacheEntry::STATUS_FREE_CHANGE; - } - } else { - // Otherwise, we need to reset to ensure we use the whole thing. - // Can't tell how much is used. - // TODO: We could tell for texcoord UV gen, and apply scale to max? - nextTexture_->maxSeenV = 512; - } + nextTexture_ = nullptr; + + UpdateMaxSeenV(entry, gstate.isModeThrough()); + + if (nextNeedsRebuild_) { + if (nextNeedsRehash_) { + // Update the hash on the texture. + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); + entry->fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); } - - imageView = nextTexture_->vkTex->texture_->GetImageView(); + if (nextNeedsChange_) { + // This texture existed previously, let's handle the change. + HandleTextureChange(entry, nextChangeReason_, false, true); + } + + // We actually build afterward (shared with rehash rebuild.) + } else if (nextNeedsRehash_) { + // Okay, this matched and didn't change - but let's check the hash. Maybe it will change. + bool doDelete = true; + if (!CheckFullHash(entry, doDelete)) { + HandleTextureChange(entry, "hash fail", true, doDelete); + nextNeedsRebuild_ = true; + } else if (nextTexture_ != nullptr) { + // Secondary cache picked a different texture, use it. + entry = nextTexture_; + nextTexture_ = nullptr; + UpdateMaxSeenV(entry, gstate.isModeThrough()); + } + } + + // Okay, now actually rebuild the texture if needed. + if (nextNeedsRebuild_) { + BuildTexture(entry, uploadBuffer); + } + + entry->lastFrame = gpuStats.numFlips; + VkCommandBuffer cmd = nullptr; + if (entry->framebuffer) { + ApplyTextureFramebuffer(cmd, entry, entry->framebuffer, imageView, sampler); + } else if (entry->vkTex) { + imageView = entry->vkTex->texture_->GetImageView(); SamplerCacheKey key; - UpdateSamplingParams(*nextTexture_, key); + UpdateSamplingParams(*entry, key); sampler = samplerCache_.GetOrCreateSampler(key); - lastBoundTexture = nextTexture_->vkTex; + gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; + gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; + + lastBoundTexture = entry->vkTex; } else { imageView = VK_NULL_HANDLE; sampler = VK_NULL_HANDLE; } - - nextTexture_ = nullptr; } void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEntry *entry, VirtualFramebuffer *framebuffer, VkImageView &imageView, VkSampler &sampler) { @@ -836,7 +854,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEn */ SamplerCacheKey key; - UpdateSamplingParams(*nextTexture_, key); + UpdateSamplingParams(*entry, key); key.mipEnable = false; sampler = samplerCache_.GetOrCreateSampler(key); @@ -871,7 +889,6 @@ bool TextureCacheVulkan::SetOffsetTexture(u32 offset) { if (success && entry->framebuffer) { // This will not apply the texture immediately. SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return true; } @@ -906,7 +923,7 @@ VkFormat ToVulkanFormat(ReplacedTextureFormat fmt) { } } -void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { +void TextureCacheVulkan::SetTexture() { #ifdef DEBUG_TEXTURES if (SetDebugTexture()) { // A different texture was bound, let's rebind next time. @@ -954,7 +971,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { u8 maxLevel = gstate.getTextureMaxLevel(); u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr)); - u32 fullhash = 0; TexCache::iterator iter = cache.find(cachekey); TexCacheEntry *entry = NULL; @@ -975,7 +991,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { } SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return; } else { // Make sure we re-evaluate framebuffers. @@ -986,7 +1001,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { } bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE; - bool doDelete = true; // First let's see if another texture with the same address had a hashfail. if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) { @@ -1023,67 +1037,10 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { rehash = true; } - bool hashFail = false; if (texhash != entry->hash) { - fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); - hashFail = true; - rehash = false; - } - - if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) { - fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); - if (fullhash != entry->fullhash) { - hashFail = true; - } else { - if (g_Config.bTextureBackoffCache) { - if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) { - // Reset to STATUS_HASHING. - entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); - entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) { - entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } - } - - if (hashFail) { match = false; - reason = "hash fail"; - entry->status |= TexCacheEntry::STATUS_UNRELIABLE; - if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) { - if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) { - entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE; - } else { - entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; - } - } - 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 (g_Config.bTextureSecondaryCache) { - 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); - secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry); - secondCache[secondKey] = *entry; - doDelete = false; - } - } - } + } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { + rehash = false; } } @@ -1098,41 +1055,27 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { if (match) { // TODO: Mark the entry reliable if it's been safe for long enough? //got one! - entry->lastFrame = gpuStats.numFlips; if (entry->vkTex != lastBoundTexture) { - gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; - gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; + gstate_c.curTextureWidth = w; + gstate_c.curTextureHeight = h; } + if (rehash) { + // Update in case any of these changed. + entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; + entry->bufw = bufw; + entry->cluthash = cluthash; + } + nextTexture_ = entry; + nextNeedsRehash_ = rehash; + nextNeedsChange_ = false; + // Might need a rebuild if the hash fails. + nextNeedsRebuild_= false; VERBOSE_LOG(G3D, "Texture at %08x Found in Cache, applying", texaddr); return; //Done! } else { - cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry); - entry->numInvalidated++; - gpuStats.numTextureInvalidations++; - DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", texaddr, reason); - if (doDelete) { - if (entry->vkTex == lastBoundTexture) { - lastBoundTexture = nullptr; - } - delete entry->vkTex; - entry->vkTex = nullptr; - } - // Clear the reliable bit if set. - if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); - } - - // Also, mark any textures with the same address but different clut. They need rechecking. - if (cluthash != 0) { - const u64 cachekeyMin = (u64)(texaddr & 0x3FFFFFFF) << 32; - const u64 cachekeyMax = cachekeyMin + (1ULL << 32); - for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) { - if (it->second.cluthash != cluthash) { - it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK; - } - } - } + nextChangeReason_ = reason; + nextNeedsChange_ = true; } } else { VERBOSE_LOG(G3D, "No texture in cache, decoding..."); @@ -1149,43 +1092,134 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { } else { entry->status = TexCacheEntry::STATUS_UNRELIABLE; } - } - if ((bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && texaddr >= PSP_GetKernelMemoryEnd()) { - ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff); - // Proceeding here can cause a crash. - nextTexture_ = nullptr; - return; + nextNeedsChange_ = false; } // We have to decode it, let's setup the cache entry first. entry->addr = texaddr; entry->hash = texhash; + entry->dim = dim; entry->format = format; - entry->lastFrame = gpuStats.numFlips; - entry->framebuffer = 0; entry->maxLevel = maxLevel; - entry->lodBias = 0.0f; - - entry->dim = gstate.getTextureDimension(0); - entry->bufw = bufw; // This would overestimate the size in many case so we underestimate instead // to avoid excessive clearing caused by cache invalidations. entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; + entry->bufw = bufw; - entry->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash; entry->cluthash = cluthash; - entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; - gstate_c.curTextureWidth = w; gstate_c.curTextureHeight = h; + nextTexture_ = entry; + nextNeedsRehash_ = true; + nextNeedsRebuild_= true; +} + +bool TextureCacheVulkan::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) { + bool hashFail = false; + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); + u32 fullhash = QuickTexHash(replacer, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + if (fullhash != entry->fullhash) { + hashFail = true; + } else { + if (g_Config.bTextureBackoffCache) { + if (entry->GetHashStatus() != TexCacheEntry::STATUS_HASHING && entry->numFrames > TexCacheEntry::FRAMES_REGAIN_TRUST) { + // Reset to STATUS_HASHING. + entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); + entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } else if (entry->numFrames > TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST) { + entry->status &= ~TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } + + if (hashFail) { + entry->status |= TexCacheEntry::STATUS_UNRELIABLE; + if (entry->numFrames < TEXCACHE_FRAME_CHANGE_FREQUENT) { + if (entry->status & TexCacheEntry::STATUS_FREE_CHANGE) { + entry->status &= ~TexCacheEntry::STATUS_FREE_CHANGE; + } else { + entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; + } + } + 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 (g_Config.bTextureSecondaryCache) { + if (entry->numInvalidated > 2 && entry->numInvalidated < 128 && !lowMemoryMode_) { + u64 secondKey = fullhash | (u64)entry->cluthash << 32; + TexCache::iterator secondIter = secondCache.find(secondKey); + if (secondIter != secondCache.end()) { + TexCacheEntry *secondEntry = &secondIter->second; + if (secondEntry->Matches(entry->dim, entry->format, entry->maxLevel)) { + // Reset the numInvalidated value lower, we got a match. + if (entry->numInvalidated > 8) { + --entry->numInvalidated; + } + nextTexture_ = secondEntry; + return true; + } + } else { + secondKey = entry->fullhash | ((u64)entry->cluthash << 32); + secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry); + secondCache[secondKey] = *entry; + doDelete = false; + } + } + } + + // We know it failed, so update the full hash right away. + entry->fullhash = fullhash; + return false; + } + + return true; +} + +bool TextureCacheVulkan::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) { + cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry); + entry->numInvalidated++; + gpuStats.numTextureInvalidations++; + DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason); + if (doDelete) { + if (entry->vkTex == lastBoundTexture) { + lastBoundTexture = nullptr; + } + delete entry->vkTex; + entry->vkTex = nullptr; + } + // Clear the reliable bit if set. + if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { + entry->SetHashStatus(TexCacheEntry::STATUS_HASHING); + } + + // Also, mark any textures with the same address but different clut. They need rechecking. + if (entry->cluthash != 0) { + const u64 cachekeyMin = (u64)(entry->addr & 0x3FFFFFFF) << 32; + const u64 cachekeyMax = cachekeyMin + (1ULL << 32); + for (auto it = cache.lower_bound(cachekeyMin), end = cache.upper_bound(cachekeyMax); it != end; ++it) { + if (it->second.cluthash != entry->cluthash) { + it->second.status |= TexCacheEntry::STATUS_CLUT_RECHECK; + } + } + } + + return false; +} + +void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry,VulkanPushBuffer *uploadBuffer) { + entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; + // For the estimate, we assume cluts always point to 8888 for simplicity. cacheSizeEstimate_ += EstimateTexMemoryUsage(entry); // Before we go reading the texture from memory, let's check for render-to-texture. + entry->framebuffer = 0; for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { auto framebuffer = fbCache_[i]; AttachFramebuffer(entry, framebuffer->fb_address, framebuffer); @@ -1194,13 +1228,19 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { // If we ended up with a framebuffer, attach it - no texture decoding needed. if (entry->framebuffer) { SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; + return; + } + + if ((entry->bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && entry->addr >= PSP_GetKernelMemoryEnd()) { + ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff); + // Proceeding here can cause a crash. return; } // Adjust maxLevel to actually present levels.. bool badMipSizes = false; - for (u32 i = 0; i <= maxLevel; i++) { + int maxLevel = entry->maxLevel; + for (int i = 0; i <= maxLevel; i++) { // If encountering levels pointing to nothing, adjust max level. u32 levelTexaddr = gstate.getTextureAddress(i); if (!Memory::IsValidAddress(levelTexaddr)) { @@ -1223,11 +1263,8 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { maxLevel = 0; } - // Disable mipmapping. Something is wrong. - // maxLevel = 0; - // If GLES3 is available, we can preallocate the storage, which makes texture loading more efficient. - VkFormat dstFmt = GetDestFormat(format, gstate.getClutPaletteFormat()); + VkFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); int scaleFactor = standardScaleFactor_; @@ -1237,12 +1274,16 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } + u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0; + int w = gstate.getTextureWidth(0); + int h = gstate.getTextureHeight(0); ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h); if (replaced.GetSize(0, w, h)) { // We're replacing, so we won't scale. scaleFactor = 1; if (g_Config.bMipMap) { maxLevel = replaced.MaxLevel(); + badMipSizes = false; } } @@ -1333,8 +1374,8 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { if (replacer.Enabled() && !replaced.Valid()) { replacedInfo.cachekey = cachekey; replacedInfo.hash = entry->fullhash; - replacedInfo.addr = texaddr; - replacedInfo.isVideo = videos_.find(texaddr & 0x3FFFFFFF) != videos_.end(); + replacedInfo.addr = entry->addr; + replacedInfo.isVideo = videos_.find(entry->addr & 0x3FFFFFFF) != videos_.end(); replacedInfo.isFinal = (entry->status & TexCacheEntry::STATUS_TO_SCALE) == 0; replacedInfo.scaleFactor = scaleFactor; replacedInfo.fmt = FromVulkanFormat(actualFmt); @@ -1374,8 +1415,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; - - nextTexture_ = entry; } VkFormat TextureCacheVulkan::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const { diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 0bd7368c19..b4a091ac3a 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -86,7 +86,7 @@ public: TextureCacheVulkan(VulkanContext *vulkan); ~TextureCacheVulkan(); - void SetTexture(VulkanPushBuffer *uploadBuffer); + void SetTexture(); virtual bool SetOffsetTexture(u32 offset) override; void Clear(bool delete_them); @@ -118,7 +118,7 @@ public: gstate_c.textureChanged |= TEXCHANGE_PARAMSONLY; } - void ApplyTexture(VkImageView &imageView, VkSampler &sampler); + void ApplyTexture(VulkanPushBuffer *uploadBuffer, VkImageView &imageView, VkSampler &sampler); protected: void DownloadFramebufferForClut(u32 clutAddr, u32 bytes); @@ -141,6 +141,10 @@ private: void ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEntry *entry, VirtualFramebuffer *framebuffer, VkImageView &image, VkSampler &sampler); void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key); + bool CheckFullHash(TexCacheEntry *const entry, bool &doDelete); + bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete); + void BuildTexture(TexCacheEntry *const entry, VulkanPushBuffer *uploadBuffer); + VulkanContext *vulkan_; VulkanDeviceAllocator *allocator_; @@ -170,6 +174,11 @@ private: DepalShaderCacheVulkan *depalShaderCache_; ShaderManagerVulkan *shaderManager_; DrawEngineVulkan *transformDraw_; + + const char *nextChangeReason_; + bool nextNeedsRehash_; + bool nextNeedsChange_; + bool nextNeedsRebuild_; }; VkFormat getClutDestFormatVulkan(GEPaletteFormat format);