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 e3814c6b3c..5c0ba999fb 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(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); diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index d53e9fe785..e97e399775 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -766,23 +766,60 @@ void TextureCache::SetTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffe } void TextureCache::ApplyTexture() { - if (nextTexture_ == nullptr) { + TexCacheEntry *entry = nextTexture_; + if (entry == nullptr) { return; } + nextTexture_ = nullptr; - if (nextTexture_->framebuffer) { - ApplyTextureFramebuffer(nextTexture_, nextTexture_->framebuffer); - } else { - UpdateMaxSeenV(gstate.isModeThrough()); + UpdateMaxSeenV(entry, gstate.isModeThrough()); - if (nextTexture_->textureName != lastBoundTexture) { - glBindTexture(GL_TEXTURE_2D, nextTexture_->textureName); - lastBoundTexture = nextTexture_->textureName; + 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()); } - UpdateSamplingParams(*nextTexture_, false); } - 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 { + 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 +1023,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; } @@ -1072,13 +1108,11 @@ 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; @@ -1094,7 +1128,6 @@ void TextureCache::SetTexture(bool force) { } SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return; } else { // Make sure we re-evaluate framebuffers. @@ -1105,7 +1138,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) { @@ -1142,67 +1174,10 @@ 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; - 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; } } @@ -1217,46 +1192,27 @@ 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; } + 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->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; - } - } - } + nextChangeReason_ = reason; + nextNeedsChange_ = true; } } else { VERBOSE_LOG(G3D, "No texture in cache, decoding..."); @@ -1273,38 +1229,136 @@ void TextureCache::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); - // Proceeding here can cause a crash. - 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 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; + + 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(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); @@ -1314,6 +1368,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 +1377,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 +1416,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 +1426,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,11 +1524,7 @@ 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); //glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index 10cddc6dcb..5f2e632fae 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -107,6 +107,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); + std::vector nameCache_; TexCache secondCache; u32 secondCacheSizeEstimate_; @@ -133,6 +137,11 @@ private: DepalShaderCache *depalShaderCache_; ShaderManager *shaderManager_; DrawEngineGLES *transformDraw_; + + const char *nextChangeReason_; + bool nextNeedsRehash_; + bool nextNeedsChange_; + bool nextNeedsRebuild_; }; GLenum getClutDestFormat(GEPaletteFormat format); diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 00e769ada1..6c8b56cc56 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -591,8 +591,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); @@ -612,19 +610,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); + textureCache_->SetTexture(); + 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_; @@ -661,6 +656,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(frame->pushUBO, 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); @@ -753,6 +756,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(frame->pushUBO, 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); diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 11a523b004..46629b61a2 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -704,50 +704,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) { @@ -841,7 +859,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEn */ SamplerCacheKey key; - UpdateSamplingParams(*nextTexture_, key); + UpdateSamplingParams(*entry, key); key.mipEnable = false; sampler = samplerCache_.GetOrCreateSampler(key); @@ -876,7 +894,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; } @@ -911,7 +928,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. @@ -959,7 +976,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; @@ -980,7 +996,6 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { } SetTextureFramebuffer(entry, entry->framebuffer); - entry->lastFrame = gpuStats.numFlips; return; } else { // Make sure we re-evaluate framebuffers. @@ -991,7 +1006,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) { @@ -1028,67 +1042,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; } } @@ -1103,41 +1060,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..."); @@ -1154,43 +1097,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); @@ -1199,13 +1233,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)) { @@ -1228,11 +1268,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_; @@ -1242,12 +1279,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; } } @@ -1338,8 +1379,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); @@ -1379,8 +1420,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);