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);