diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 9c0bc78a32..505b928500 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -37,6 +37,18 @@ // Videos should be updated every few frames, so we forge quickly. #define VIDEO_DECIMATE_AGE 4 +// If a texture hasn't been seen for this many frames, get rid of it. +#define TEXTURE_KILL_AGE 200 +#define TEXTURE_KILL_AGE_LOWMEM 60 +// Not used in lowmem mode. +#define TEXTURE_SECOND_KILL_AGE 100 + +// Try to be prime to other decimation intervals. +#define TEXCACHE_DECIMATION_INTERVAL 13 + +#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in VRAM +#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024 + TextureCacheCommon::TextureCacheCommon(Draw::DrawContext *draw) : draw_(draw), clearCacheNextFrame_(false), @@ -49,7 +61,10 @@ TextureCacheCommon::TextureCacheCommon(Draw::DrawContext *draw) clutTotalBytes_(0), clutMaxBytes_(0), clutRenderAddress_(0xFFFFFFFF), - clutAlphaLinear_(false) { + clutAlphaLinear_(false), + isBgraBackend_(false) { + decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; + // TODO: Clamp down to 256/1KB? Need to check mipmapShareClut and clamp loadclut. clutBufRaw_ = (u32 *)AllocateAlignedMemory(1024 * sizeof(u32), 16); // 4KB clutBufConverted_ = (u32 *)AllocateAlignedMemory(1024 * sizeof(u32), 16); // 4KB @@ -60,11 +75,11 @@ TextureCacheCommon::TextureCacheCommon(Draw::DrawContext *draw) clutBuf_ = clutBufConverted_; // These buffers will grow if necessary, but most won't need more than this. - tmpTexBuf32.resize(512 * 512); // 1MB - tmpTexBuf16.resize(512 * 512); // 0.5MB - tmpTexBufRearrange.resize(512 * 512); // 1MB + tmpTexBuf32_.resize(512 * 512); // 1MB + tmpTexBuf16_.resize(512 * 512); // 0.5MB + tmpTexBufRearrange_.resize(512 * 512); // 1MB - replacer.Init(); + replacer_.Init(); } TextureCacheCommon::~TextureCacheCommon() { @@ -158,6 +173,264 @@ void TextureCacheCommon::UpdateMaxSeenV(TexCacheEntry *entry, bool throughMode) } } + +void TextureCacheCommon::SetTexture(bool force) { +#ifdef DEBUG_TEXTURES + if (SetDebugTexture()) { + // A different texture was bound, let's rebind next time. + InvalidateLastTexture(); + return; + } +#endif + + if (force) { + InvalidateLastTexture(); + } + + u8 level = 0; + if (IsFakeMipmapChange()) + level = (gstate.texlevel >> 20) & 0xF; + u32 texaddr = gstate.getTextureAddress(level); + if (!Memory::IsValidAddress(texaddr)) { + // Bind a null texture and return. + Unbind(); + InvalidateLastTexture(); + return; + } + + const u16 dim = gstate.getTextureDimension(level); + int w = gstate.getTextureWidth(level); + int h = gstate.getTextureHeight(level); + + GETextureFormat format = gstate.getTextureFormat(); + if (format >= 11) { + ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format); + // TODO: Better assumption? + format = GE_TFMT_5650; + } + bool hasClut = gstate.isTextureFormatIndexed(); + + // Ignore uncached/kernel when caching. + u32 cluthash; + if (hasClut) { + if (clutLastFormat_ != gstate.clutformat) { + // We update here because the clut format can be specified after the load. + UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); + } + cluthash = clutHash_ ^ gstate.clutformat; + } else { + cluthash = 0; + } + u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash); + + int bufw = GetTextureBufw(0, texaddr, format); + u8 maxLevel = gstate.getTextureMaxLevel(); + + u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr)); + + TexCache::iterator iter = cache_.find(cachekey); + TexCacheEntry *entry = nullptr; + gstate_c.needShaderTexClamp = false; + gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE; + gstate_c.bgraTexture = isBgraBackend_; + + if (iter != cache_.end()) { + entry = iter->second.get(); + // Validate the texture still matches the cache entry. + bool match = entry->Matches(dim, format, maxLevel); + const char *reason = "different params"; + + // Check for FBO - slow! + if (entry->framebuffer) { + if (match) { + if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { + WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); + } + + SetTextureFramebuffer(entry, entry->framebuffer); + return; + } else { + // Make sure we re-evaluate framebuffers. + DetachFramebuffer(entry, texaddr, entry->framebuffer); + reason = "detached framebuf"; + match = false; + } + } + + bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE; + + // First let's see if another texture with the same address had a hashfail. + if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) { + // Always rehash in this case, if one changed the rest all probably did. + rehash = true; + entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK; + } else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) { + // Okay, just some parameter change - the data didn't change, no need to rehash. + rehash = false; + } + + if (match) { + if (entry->lastFrame != gpuStats.numFlips) { + u32 diff = gpuStats.numFlips - entry->lastFrame; + entry->numFrames++; + + if (entry->framesUntilNextFullHash < diff) { + // Exponential backoff up to 512 frames. Textures are often reused. + if (entry->numFrames > 32) { + // Also, try to add some "randomness" to avoid rehashing several textures the same frame. + entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + (entry->textureName & 15); + } else { + entry->framesUntilNextFullHash = entry->numFrames; + } + rehash = true; + } else { + entry->framesUntilNextFullHash -= diff; + } + } + + // If it's not huge or has been invalidated many times, recheck the whole texture. + if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) { + entry->invalidHint = 0; + rehash = true; + } + + if (texhash != entry->hash) { + match = false; + } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { + rehash = false; + } + } + + if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) { + if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { + // INFO_LOG(G3D, "Reloading texture to do the scaling we skipped.."); + match = false; + reason = "scaling"; + } + } + + if (match) { + // TODO: Mark the entry reliable if it's been safe for long enough? + //got one! + 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 { + nextChangeReason_ = reason; + nextNeedsChange_ = true; + } + } else { + VERBOSE_LOG(G3D, "No texture in cache, decoding..."); + TexCacheEntry *entryNew = new TexCacheEntry{}; + cache_[cachekey].reset(entryNew); + + if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { + WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); + } + + entry = entryNew; + if (g_Config.bTextureBackoffCache) { + entry->status = TexCacheEntry::STATUS_HASHING; + } else { + entry->status = TexCacheEntry::STATUS_UNRELIABLE; + } + + 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->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->cluthash = cluthash; + + gstate_c.curTextureWidth = w; + gstate_c.curTextureHeight = h; + + // Before we go reading the texture from memory, let's check for render-to-texture. + // We must do this early so we have the right w/h. + entry->framebuffer = 0; + for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { + auto framebuffer = fbCache_[i]; + AttachFramebuffer(entry, framebuffer->fb_address, framebuffer); + } + + // If we ended up with a framebuffer, attach it - no texture decoding needed. + if (entry->framebuffer) { + SetTextureFramebuffer(entry, entry->framebuffer); + } + + nextTexture_ = entry; + nextNeedsRehash_ = entry->framebuffer == nullptr; + // We still need to rebuild, to allocate a texture. But we'll bail early. + nextNeedsRebuild_ = true; +} + +// Removes old textures. +void TextureCacheCommon::Decimate() { + if (--decimationCounter_ <= 0) { + decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; + } else { + return; + } + + if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) { + const u32 had = cacheSizeEstimate_; + + ForgetLastTexture(); + int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE; + for (TexCache::iterator iter = cache_.begin(); iter != cache_.end(); ) { + if (iter->second->lastFrame + killAge < gpuStats.numFlips) { + DeleteTexture(iter++); + } else { + ++iter; + } + } + + VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_); + } + + if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) { + const u32 had = secondCacheSizeEstimate_; + + for (TexCache::iterator iter = secondCache_.begin(); iter != secondCache_.end(); ) { + // In low memory mode, we kill them all. + if (lowMemoryMode_ || iter->second->lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.numFlips) { + ReleaseTexture(iter->second.get()); + secondCacheSizeEstimate_ -= EstimateTexMemoryUsage(iter->second.get()); + secondCache_.erase(iter++); + } else { + ++iter; + } + } + + VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_); + } + + DecimateVideos(); +} + void TextureCacheCommon::DecimateVideos() { if (!videos_.empty()) { for (auto iter = videos_.begin(); iter != videos_.end(); ) { @@ -170,6 +443,43 @@ void TextureCacheCommon::DecimateVideos() { } } +bool TextureCacheCommon::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 && (entry->status & TexCacheEntry::STATUS_IS_SCALED) == 0) { + // 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 { + InvalidateLastTexture(entry); + ReleaseTexture(entry); + entry->status &= ~TexCacheEntry::STATUS_IS_SCALED; + } + } + // 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 TextureCacheCommon::NotifyFramebuffer(u32 address, VirtualFramebuffer *framebuffer, FramebufferNotification msg) { // Must be in VRAM so | 0x04000000 it is. Also, ignore memory mirrors. // These checks are mainly to reduce scanning all textures. @@ -191,15 +501,15 @@ void TextureCacheCommon::NotifyFramebuffer(u32 address, VirtualFramebuffer *fram if (std::find(fbCache_.begin(), fbCache_.end(), framebuffer) == fbCache_.end()) { fbCache_.push_back(framebuffer); } - for (auto it = cache.lower_bound(cacheKey), end = cache.upper_bound(cacheKeyEnd); it != end; ++it) { - AttachFramebuffer(&it->second, addr, framebuffer); + for (auto it = cache_.lower_bound(cacheKey), end = cache_.upper_bound(cacheKeyEnd); it != end; ++it) { + AttachFramebuffer(it->second.get(), addr, framebuffer); } // Let's assume anything in mirrors is fair game to check. - for (auto it = cache.lower_bound(mirrorCacheKey), end = cache.upper_bound(mirrorCacheKeyEnd); it != end; ++it) { + for (auto it = cache_.lower_bound(mirrorCacheKey), end = cache_.upper_bound(mirrorCacheKeyEnd); it != end; ++it) { const u64 mirrorlessKey = it->first & ~0x0060000000000000ULL; // Let's still make sure it's in the cache range. if (mirrorlessKey >= cacheKey && mirrorlessKey <= cacheKeyEnd) { - AttachFramebuffer(&it->second, addr, framebuffer); + AttachFramebuffer(it->second.get(), addr, framebuffer); } } break; @@ -214,7 +524,7 @@ void TextureCacheCommon::NotifyFramebuffer(u32 address, VirtualFramebuffer *fram // We might erase, so move to the next one already (which won't become invalid.) ++it; - DetachFramebuffer(&cache[cachekey], addr, framebuffer); + DetachFramebuffer(cache_[cachekey].get(), addr, framebuffer); } break; } @@ -239,7 +549,7 @@ void TextureCacheCommon::AttachFramebufferValid(TexCacheEntry *entry, VirtualFra } entry->framebuffer = framebuffer; entry->invalidHint = 0; - entry->status &= ~TextureCacheCommon::TexCacheEntry::STATUS_DEPALETTIZE; + entry->status &= ~TexCacheEntry::STATUS_DEPALETTIZE; entry->maxLevel = 0; fbTexInfo_[cachekey] = fbInfo; framebuffer->last_frame_attached = gpuStats.numFlips; @@ -258,7 +568,7 @@ void TextureCacheCommon::AttachFramebufferInvalid(TexCacheEntry *entry, VirtualF } entry->framebuffer = framebuffer; entry->invalidHint = -1; - entry->status &= ~TextureCacheCommon::TexCacheEntry::STATUS_DEPALETTIZE; + entry->status &= ~TexCacheEntry::STATUS_DEPALETTIZE; entry->maxLevel = 0; fbTexInfo_[cachekey] = fbInfo; host->GPUNotifyTextureAttachment(entry->addr); @@ -432,11 +742,11 @@ bool TextureCacheCommon::SetOffsetTexture(u32 offset) { const u16 dim = gstate.getTextureDimension(0); u64 cachekey = TexCacheEntry::CacheKey(texaddr, gstate.getTextureFormat(), dim, 0); - TexCache::iterator iter = cache.find(cachekey); - if (iter == cache.end()) { + TexCache::iterator iter = cache_.find(cachekey); + if (iter == cache_.end()) { return false; } - TexCacheEntry *entry = &iter->second; + TexCacheEntry *entry = iter->second.get(); bool success = false; for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { @@ -494,7 +804,7 @@ void TextureCacheCommon::NotifyConfigChanged() { standardScaleFactor_ = scaleFactor; - replacer.NotifyConfigChanged(); + replacer_.NotifyConfigChanged(); } void TextureCacheCommon::NotifyVideoUpload(u32 addr, int size, int width, GEBufferFormat fmt) { @@ -685,9 +995,9 @@ bool TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureForm const int clutSharingOffset = mipmapShareClut ? 0 : level * 16; if (swizzled) { - tmpTexBuf32.resize(bufw * ((h + 7) & ~7)); - UnswizzleFromMem(tmpTexBuf32.data(), bufw / 2, texptr, bufw, h, 0); - texptr = (u8 *)tmpTexBuf32.data(); + tmpTexBuf32_.resize(bufw * ((h + 7) & ~7)); + UnswizzleFromMem(tmpTexBuf32_.data(), bufw / 2, texptr, bufw, h, 0); + texptr = (u8 *)tmpTexBuf32_.data(); } switch (clutformat) { @@ -770,9 +1080,9 @@ bool TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureForm } } else { // We don't have enough space for all rows in out, so use a temp buffer. - tmpTexBuf32.resize(bufw * ((h + 7) & ~7)); - UnswizzleFromMem(tmpTexBuf32.data(), bufw * 2, texptr, bufw, h, 2); - const u8 *unswizzled = (u8 *)tmpTexBuf32.data(); + tmpTexBuf32_.resize(bufw * ((h + 7) & ~7)); + UnswizzleFromMem(tmpTexBuf32_.data(), bufw * 2, texptr, bufw, h, 2); + const u8 *unswizzled = (u8 *)tmpTexBuf32_.data(); if (reverseColors) { for (int y = 0; y < h; ++y) { @@ -804,9 +1114,9 @@ bool TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureForm } } else { // We don't have enough space for all rows in out, so use a temp buffer. - tmpTexBuf32.resize(bufw * ((h + 7) & ~7)); - UnswizzleFromMem(tmpTexBuf32.data(), bufw * 4, texptr, bufw, h, 4); - const u8 *unswizzled = (u8 *)tmpTexBuf32.data(); + tmpTexBuf32_.resize(bufw * ((h + 7) & ~7)); + UnswizzleFromMem(tmpTexBuf32_.data(), bufw * 4, texptr, bufw, h, 4); + const u8 *unswizzled = (u8 *)tmpTexBuf32_.data(); if (reverseColors) { for (int y = 0; y < h; ++y) { @@ -899,9 +1209,9 @@ bool TextureCacheCommon::ReadIndexedTex(u8 *out, int outPitch, int level, const int h = gstate.getTextureHeight(level); if (gstate.isTextureSwizzled()) { - tmpTexBuf32.resize(bufw * ((h + 7) & ~7)); - UnswizzleFromMem(tmpTexBuf32.data(), bufw * bytesPerIndex, texptr, bufw, h, bytesPerIndex); - texptr = (u8 *)tmpTexBuf32.data(); + tmpTexBuf32_.resize(bufw * ((h + 7) & ~7)); + UnswizzleFromMem(tmpTexBuf32_.data(), bufw * bytesPerIndex, texptr, bufw, h, bytesPerIndex); + texptr = (u8 *)tmpTexBuf32_.data(); } switch (gstate.getClutPaletteFormat()) { @@ -966,11 +1276,93 @@ bool TextureCacheCommon::ReadIndexedTex(u8 *out, int outPitch, int level, const } -bool TextureCacheCommon::CheckFullHash(TexCacheEntry *const entry, bool &doDelete) { +void TextureCacheCommon::ApplyTexture() { + TexCacheEntry *entry = nextTexture_; + if (entry == nullptr) { + return; + } + nextTexture_ = nullptr; + + UpdateMaxSeenV(entry, gstate.isModeThrough()); + + 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()); + } + } + + // 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 { + BindTexture(entry); + gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; + gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; + } +} + +void TextureCacheCommon::Clear(bool delete_them) { + ForgetLastTexture(); + if (delete_them) { + for (TexCache::iterator iter = cache_.begin(); iter != cache_.end(); ++iter) { + ReleaseTexture(iter->second.get()); + } + for (TexCache::iterator iter = secondCache_.begin(); iter != secondCache_.end(); ++iter) { + ReleaseTexture(iter->second.get()); + } + } + if (cache_.size() + secondCache_.size()) { + INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache_.size() + secondCache_.size())); + cache_.clear(); + secondCache_.clear(); + cacheSizeEstimate_ = 0; + secondCacheSizeEstimate_ = 0; + } + fbTexInfo_.clear(); + videos_.clear(); +} + +void TextureCacheCommon::DeleteTexture(TexCache::iterator it) { + ReleaseTexture(it->second.get()); + auto fbInfo = fbTexInfo_.find(it->first); + if (fbInfo != fbTexInfo_.end()) { + fbTexInfo_.erase(fbInfo); + } + cacheSizeEstimate_ -= EstimateTexMemoryUsage(it->second.get()); + cache_.erase(it); +} + +bool TextureCacheCommon::CheckFullHash(TexCacheEntry *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); + u32 fullhash = QuickTexHash(replacer_, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); if (fullhash != entry->fullhash) { hashFail = true; } else { @@ -1001,9 +1393,9 @@ bool TextureCacheCommon::CheckFullHash(TexCacheEntry *const entry, bool &doDelet 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; + TexCache::iterator secondIter = secondCache_.find(secondKey); + if (secondIter != secondCache_.end()) { + TexCacheEntry *secondEntry = secondIter->second.get(); if (secondEntry->Matches(entry->dim, entry->format, entry->maxLevel)) { // Reset the numInvalidated value lower, we got a match. if (entry->numInvalidated > 8) { @@ -1015,7 +1407,8 @@ bool TextureCacheCommon::CheckFullHash(TexCacheEntry *const entry, bool &doDelet } else { secondKey = entry->fullhash | ((u64)entry->cluthash << 32); secondCacheSizeEstimate_ += EstimateTexMemoryUsage(entry); - secondCache[secondKey] = *entry; + // Is this wise? We simply copy the entry. + secondCache_[secondKey].reset(new TexCacheEntry(*entry)); doDelete = false; } } @@ -1046,28 +1439,28 @@ void TextureCacheCommon::Invalidate(u32 addr, int size, GPUInvalidationType type endKey = (u64)-1; } - for (TexCache::iterator iter = cache.lower_bound(startKey), end = cache.upper_bound(endKey); iter != end; ++iter) { - u32 texAddr = iter->second.addr; - u32 texEnd = iter->second.addr + iter->second.sizeInRAM; + for (TexCache::iterator iter = cache_.lower_bound(startKey), end = cache_.upper_bound(endKey); iter != end; ++iter) { + u32 texAddr = iter->second->addr; + u32 texEnd = iter->second->addr + iter->second->sizeInRAM; if (texAddr < addr_end && addr < texEnd) { - if (iter->second.GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - iter->second.SetHashStatus(TexCacheEntry::STATUS_HASHING); + if (iter->second->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { + iter->second->SetHashStatus(TexCacheEntry::STATUS_HASHING); } if (type != GPU_INVALIDATE_ALL) { gpuStats.numTextureInvalidations++; // Start it over from 0 (unless it's safe.) - iter->second.numFrames = type == GPU_INVALIDATE_SAFE ? 256 : 0; + iter->second->numFrames = type == GPU_INVALIDATE_SAFE ? 256 : 0; if (type == GPU_INVALIDATE_SAFE) { - u32 diff = gpuStats.numFlips - iter->second.lastFrame; + u32 diff = gpuStats.numFlips - iter->second->lastFrame; // We still need to mark if the texture is frequently changing, even if it's safely changing. if (diff < TEXCACHE_FRAME_CHANGE_FREQUENT) { - iter->second.status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; + iter->second->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; } } - iter->second.framesUntilNextFullHash = 0; - } else if (!iter->second.framebuffer) { - iter->second.invalidHint++; + iter->second->framesUntilNextFullHash = 0; + } else if (!iter->second->framebuffer) { + iter->second->invalidHint++; } } } @@ -1084,12 +1477,12 @@ void TextureCacheCommon::InvalidateAll(GPUInvalidationType /*unused*/) { } timesInvalidatedAllThisFrame_++; - for (TexCache::iterator iter = cache.begin(), end = cache.end(); iter != end; ++iter) { - if (iter->second.GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - iter->second.SetHashStatus(TexCacheEntry::STATUS_HASHING); + for (TexCache::iterator iter = cache_.begin(), end = cache_.end(); iter != end; ++iter) { + if (iter->second->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { + iter->second->SetHashStatus(TexCacheEntry::STATUS_HASHING); } - if (!iter->second.framebuffer) { - iter->second.invalidHint++; + if (!iter->second->framebuffer) { + iter->second->invalidHint++; } } } diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index e9987e9b18..1789d05e66 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -19,6 +19,7 @@ #include #include +#include #include "Common/CommonTypes.h" #include "Common/MemoryUtil.h" @@ -45,6 +46,8 @@ enum FramebufferNotification { // Note: only used when hash backoff is disabled. #define TEXCACHE_FRAME_CHANGE_FREQUENT_REGAIN_TRUST 33 +#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame + struct VirtualFramebuffer; class CachedTextureVulkan; @@ -76,7 +79,96 @@ struct SamplerCacheKey { } }; + +// Wow this is starting to grow big. Soon need to start looking at resizing it. +// Must stay a POD. +struct TexCacheEntry { + // After marking STATUS_UNRELIABLE, if it stays the same this many frames we'll trust it again. + const static int FRAMES_REGAIN_TRUST = 1000; + + enum Status { + STATUS_HASHING = 0x00, + STATUS_RELIABLE = 0x01, // Don't bother rehashing. + STATUS_UNRELIABLE = 0x02, // Always recheck hash. + STATUS_MASK = 0x03, + + STATUS_ALPHA_UNKNOWN = 0x04, + STATUS_ALPHA_FULL = 0x00, // Has no alpha channel, or always full alpha. + STATUS_ALPHA_SIMPLE = 0x08, // Like above, but also has 0 alpha (e.g. 5551.) + STATUS_ALPHA_MASK = 0x0c, + + STATUS_CHANGE_FREQUENT = 0x10, // Changes often (less than 6 frames in between.) + STATUS_CLUT_RECHECK = 0x20, // Another texture with same addr had a hashfail. + STATUS_DEPALETTIZE = 0x40, // Needs to go through a depalettize pass. + STATUS_TO_SCALE = 0x80, // Pending texture scaling in a later frame. + STATUS_IS_SCALED = 0x100, // Has been scaled (can't be replaceImages'd.) + STATUS_FREE_CHANGE = 0x200, // Allow one change before marking "frequent". + }; + + // Status, but int so we can zero initialize. + int status; + u32 addr; + u32 hash; + VirtualFramebuffer *framebuffer; // if null, not sourced from an FBO. + u32 sizeInRAM; + int lastFrame; + int numFrames; + int numInvalidated; + u32 framesUntilNextFullHash; + u8 format; + u8 maxLevel; + u16 dim; + u16 bufw; + union { + u32 textureName; + void *texturePtr; + CachedTextureVulkan *vkTex; + }; +#ifdef _WIN32 + void *textureView; // Used by D3D11 only for the shader resource view. +#endif + int invalidHint; + u32 fullhash; + u32 cluthash; + float lodBias; + u16 maxSeenV; + + // Cache the current filter settings so we can avoid setting it again. + // (OpenGL madness where filter settings are attached to each texture. Unused in Vulkan). + u8 magFilt; + u8 minFilt; + bool sClamp; + bool tClamp; + + Status GetHashStatus() { + return Status(status & STATUS_MASK); + } + void SetHashStatus(Status newStatus) { + status = (status & ~STATUS_MASK) | newStatus; + } + Status GetAlphaStatus() { + return Status(status & STATUS_ALPHA_MASK); + } + void SetAlphaStatus(Status newStatus) { + status = (status & ~STATUS_ALPHA_MASK) | newStatus; + } + void SetAlphaStatus(Status newStatus, int level) { + // For non-level zero, only set more restrictive. + if (newStatus == STATUS_ALPHA_UNKNOWN || level == 0) { + SetAlphaStatus(newStatus); + } else if (newStatus == STATUS_ALPHA_SIMPLE && GetAlphaStatus() == STATUS_ALPHA_FULL) { + SetAlphaStatus(STATUS_ALPHA_SIMPLE); + } + } + + bool Matches(u16 dim2, u8 format2, u8 maxLevel2) const; + u64 CacheKey() const; + static u64 CacheKey(u32 addr, u8 format, u16 dim, u32 cluthash); +}; + class FramebufferManagerCommon; +// Can't be unordered_map, we use lower_bound ... although for some reason that compiles on MSVC. +typedef std::map> TexCache; class TextureCacheCommon { public: @@ -86,11 +178,16 @@ public: void LoadClut(u32 clutAddr, u32 loadBytes); bool GetCurrentClutBuffer(GPUDebugBuffer &buffer); + void SetTexture(bool force = false); + void ApplyTexture(); bool SetOffsetTexture(u32 offset); void Invalidate(u32 addr, int size, GPUInvalidationType type); void InvalidateAll(GPUInvalidationType type); void ClearNextFrame(); + virtual void ForgetLastTexture() = 0; + virtual void InvalidateLastTexture(TexCacheEntry *entry = nullptr) = 0; + virtual void Clear(bool delete_them); // FramebufferManager keeps TextureCache updated about what regions of memory are being rendered to. void NotifyFramebuffer(u32 address, VirtualFramebuffer *framebuffer, FramebufferNotification msg); @@ -100,106 +197,25 @@ public: int AttachedDrawingHeight(); size_t NumLoadedTextures() const { - return cache.size(); + return cache_.size(); } bool IsFakeMipmapChange() { return PSP_CoreParameter().compat.flags().FakeMipmapChange && gstate.getTexLevelMode() == GE_TEXLEVEL_MODE_CONST; } - // Wow this is starting to grow big. Soon need to start looking at resizing it. - // Must stay a POD. - struct TexCacheEntry { - // After marking STATUS_UNRELIABLE, if it stays the same this many frames we'll trust it again. - const static int FRAMES_REGAIN_TRUST = 1000; - - enum Status { - STATUS_HASHING = 0x00, - STATUS_RELIABLE = 0x01, // Don't bother rehashing. - STATUS_UNRELIABLE = 0x02, // Always recheck hash. - STATUS_MASK = 0x03, - - STATUS_ALPHA_UNKNOWN = 0x04, - STATUS_ALPHA_FULL = 0x00, // Has no alpha channel, or always full alpha. - STATUS_ALPHA_SIMPLE = 0x08, // Like above, but also has 0 alpha (e.g. 5551.) - STATUS_ALPHA_MASK = 0x0c, - - STATUS_CHANGE_FREQUENT = 0x10, // Changes often (less than 6 frames in between.) - STATUS_CLUT_RECHECK = 0x20, // Another texture with same addr had a hashfail. - STATUS_DEPALETTIZE = 0x40, // Needs to go through a depalettize pass. - STATUS_TO_SCALE = 0x80, // Pending texture scaling in a later frame. - STATUS_IS_SCALED = 0x100, // Has been scaled (can't be replaceImages'd.) - STATUS_FREE_CHANGE = 0x200, // Allow one change before marking "frequent". - }; - - // Status, but int so we can zero initialize. - int status; - u32 addr; - u32 hash; - VirtualFramebuffer *framebuffer; // if null, not sourced from an FBO. - u32 sizeInRAM; - int lastFrame; - int numFrames; - int numInvalidated; - u32 framesUntilNextFullHash; - u8 format; - u8 maxLevel; - u16 dim; - u16 bufw; - union { - u32 textureName; - void *texturePtr; - CachedTextureVulkan *vkTex; - }; -#ifdef _WIN32 - void *textureView; // Used by D3D11 only for the shader resource view. -#endif - int invalidHint; - u32 fullhash; - u32 cluthash; - float lodBias; - u16 maxSeenV; - - // Cache the current filter settings so we can avoid setting it again. - // (OpenGL madness where filter settings are attached to each texture. Unused in Vulkan). - u8 magFilt; - u8 minFilt; - bool sClamp; - bool tClamp; - - Status GetHashStatus() { - return Status(status & STATUS_MASK); - } - void SetHashStatus(Status newStatus) { - status = (status & ~STATUS_MASK) | newStatus; - } - Status GetAlphaStatus() { - return Status(status & STATUS_ALPHA_MASK); - } - void SetAlphaStatus(Status newStatus) { - status = (status & ~STATUS_ALPHA_MASK) | newStatus; - } - void SetAlphaStatus(Status newStatus, int level) { - // For non-level zero, only set more restrictive. - if (newStatus == STATUS_ALPHA_UNKNOWN || level == 0) { - SetAlphaStatus(newStatus); - } else if (newStatus == STATUS_ALPHA_SIMPLE && GetAlphaStatus() == STATUS_ALPHA_FULL) { - SetAlphaStatus(STATUS_ALPHA_SIMPLE); - } - } - - bool Matches(u16 dim2, u8 format2, u8 maxLevel2) const; - u64 CacheKey() const; - static u64 CacheKey(u32 addr, u8 format, u16 dim, u32 cluthash); - }; - protected: + virtual void BindTexture(TexCacheEntry *entry) = 0; virtual void Unbind() = 0; + virtual void ReleaseTexture(TexCacheEntry *entry) = 0; + void DeleteTexture(TexCache::iterator it); + void Decimate(); - bool CheckFullHash(TexCacheEntry *const entry, bool &doDelete); - - // Can't be unordered_map, we use lower_bound ... although for some reason that compiles on MSVC. - typedef std::map TexCache; + virtual void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) = 0; + bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete); + virtual void BuildTexture(TexCacheEntry *const entry, bool replaceImages) = 0; + virtual void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) = 0; + bool CheckFullHash(TexCacheEntry *entry, bool &doDelete); // Separate to keep main texture cache size down. struct AttachedFramebufferInfo { @@ -248,8 +264,12 @@ protected: } } + static inline u32 MiniHash(const u32 *ptr) { + return ptr[0]; + } + Draw::DrawContext *draw_; - TextureReplacer replacer; + TextureReplacer replacer_; FramebufferManagerCommon *framebufferManager_; bool clearCacheNextFrame_; @@ -259,10 +279,10 @@ protected: int texelsScaledThisFrame_; int timesInvalidatedAllThisFrame_; - TexCache cache; + TexCache cache_; u32 cacheSizeEstimate_; - TexCache secondCache; + TexCache secondCache_; u32 secondCacheSizeEstimate_; std::vector fbCache_; @@ -270,12 +290,14 @@ protected: std::map videos_; - SimpleBuf tmpTexBuf32; - SimpleBuf tmpTexBuf16; - SimpleBuf tmpTexBufRearrange; + SimpleBuf tmpTexBuf32_; + SimpleBuf tmpTexBuf16_; + SimpleBuf tmpTexBufRearrange_; TexCacheEntry *nextTexture_; + u32 clutHash_ = 0; + // Raw is where we keep the original bytes. Converted is where we swap colors if necessary. u32 *clutBufRaw_; u32 *clutBufConverted_; @@ -296,17 +318,19 @@ protected: bool nextNeedsRehash_; bool nextNeedsChange_; bool nextNeedsRebuild_; + + bool isBgraBackend_; }; -inline bool TextureCacheCommon::TexCacheEntry::Matches(u16 dim2, u8 format2, u8 maxLevel2) const { +inline bool TexCacheEntry::Matches(u16 dim2, u8 format2, u8 maxLevel2) const { return dim == dim2 && format == format2 && maxLevel == maxLevel2; } -inline u64 TextureCacheCommon::TexCacheEntry::CacheKey() const { +inline u64 TexCacheEntry::CacheKey() const { return CacheKey(addr, format, dim, cluthash); } -inline u64 TextureCacheCommon::TexCacheEntry::CacheKey(u32 addr, u8 format, u16 dim, u32 cluthash) { +inline u64 TexCacheEntry::CacheKey(u32 addr, u8 format, u16 dim, u32 cluthash) { u64 cachekey = ((u64)(addr & 0x3FFFFFFF) << 32) | dim; bool hasClut = (format & 4) != 0; if (hasClut) { diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 8c1531d582..4fe76f1433 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -43,20 +43,6 @@ #define INVALID_TEX (ID3D11ShaderResourceView *)(-1LL) - // If a texture hasn't been seen for this many frames, get rid of it. -#define TEXTURE_KILL_AGE 200 -#define TEXTURE_KILL_AGE_LOWMEM 60 - // Not used in lowmem mode. -#define TEXTURE_SECOND_KILL_AGE 100 - - // Try to be prime to other decimation intervals. -#define TEXCACHE_DECIMATION_INTERVAL 13 - -#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame - -#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in VRAM -#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024 - static const D3D11_INPUT_ELEMENT_DESC g_QuadVertexElements[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12,}, @@ -114,8 +100,8 @@ TextureCacheD3D11::TextureCacheD3D11(Draw::DrawContext *draw) device_ = (ID3D11Device *)draw->GetNativeObject(Draw::NativeObject::DEVICE); context_ = (ID3D11DeviceContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT); + isBgraBackend_ = true; lastBoundTexture = INVALID_TEX; - decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; HRESULT result = 0; @@ -134,40 +120,17 @@ void TextureCacheD3D11::SetFramebufferManager(FramebufferManagerD3D11 *fbManager framebufferManager_ = fbManager; } -void TextureCacheD3D11::Clear(bool delete_them) { - // ID3D11ShaderResourceView *srv = nullptr; - // context_->PSSetShaderResources(0, 1, &srv); - lastBoundTexture = INVALID_TEX; - if (delete_them) { - for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter) { - DEBUG_LOG(G3D, "Deleting texture %p", iter->second.texturePtr); - ReleaseTexture(&iter->second); - } - for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ++iter) { - DEBUG_LOG(G3D, "Deleting texture %p", iter->second.texturePtr); - ReleaseTexture(&iter->second); - } +void TextureCacheD3D11::ReleaseTexture(TexCacheEntry *entry) { + ID3D11Texture2D *texture = (ID3D11Texture2D *)entry->texturePtr; + ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)entry->textureView; + if (texture) { + texture->Release(); + entry->texturePtr = nullptr; } - if (cache.size() + secondCache.size()) { - INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache.size() + secondCache.size())); - cache.clear(); - secondCache.clear(); - cacheSizeEstimate_ = 0; - secondCacheSizeEstimate_ = 0; + if (view) { + view->Release(); + entry->textureView = nullptr; } - fbTexInfo_.clear(); - videos_.clear(); -} - -void TextureCacheD3D11::DeleteTexture(TexCache::iterator it) { - ReleaseTexture(&it->second); - auto fbInfo = fbTexInfo_.find(it->first); - if (fbInfo != fbTexInfo_.end()) { - fbTexInfo_.erase(fbInfo); - } - - cacheSizeEstimate_ -= EstimateTexMemoryUsage(&it->second); - cache.erase(it); } void TextureCacheD3D11::ForgetLastTexture() { @@ -177,49 +140,10 @@ void TextureCacheD3D11::ForgetLastTexture() { context_->PSSetShaderResources(0, 1, &nullTex); } -// Removes old textures. -void TextureCacheD3D11::Decimate() { - if (--decimationCounter_ <= 0) { - decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; - } else { - return; - } - - if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) { - const u32 had = cacheSizeEstimate_; - - ID3D11ShaderResourceView *srv = nullptr; - context_->PSSetShaderResources(0, 1, &srv); +void TextureCacheD3D11::InvalidateLastTexture(TexCacheEntry *entry) { + if (!entry || entry->texturePtr == lastBoundTexture) { lastBoundTexture = INVALID_TEX; - int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE; - for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) { - if (iter->second.lastFrame + killAge < gpuStats.numFlips) { - DeleteTexture(iter++); - } else { - ++iter; - } - } - - VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_); } - - if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) { - const u32 had = secondCacheSizeEstimate_; - - for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ) { - // In low memory mode, we kill them all. - if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_KILL_AGE < gpuStats.numFlips) { - ReleaseTexture(&iter->second); - secondCache.erase(iter++); - } else { - ++iter; - } - } - - VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_); - } - - DecimateVideos(); } DXGI_FORMAT getClutDestFormatD3D11(GEPaletteFormat format) { @@ -298,10 +222,6 @@ void TextureCacheD3D11::StartFrame() { } } -static inline u32 MiniHash(const u32 *ptr) { - return ptr[0]; -} - void TextureCacheD3D11::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) { const u32 clutBaseBytes = clutBase * (clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16)); // Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier. @@ -335,8 +255,16 @@ void TextureCacheD3D11::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBa clutLastFormat_ = gstate.clutformat; } -inline u32 TextureCacheD3D11::GetCurrentClutHash() { - return clutHash_; +void TextureCacheD3D11::BindTexture(TexCacheEntry *entry) { + ID3D11ShaderResourceView *textureView = DxView(entry); + if (textureView != lastBoundTexture) { + context_->PSSetShaderResources(0, 1, &textureView); + lastBoundTexture = textureView; + } + SamplerCacheKey key; + UpdateSamplingParams(*entry, key); + ID3D11SamplerState *state = samplerCache_.GetOrCreateSampler(device_, key); + context_->PSSetSamplers(0, 1, &state); } void TextureCacheD3D11::Unbind() { @@ -344,66 +272,6 @@ void TextureCacheD3D11::Unbind() { context_->PSSetShaderResources(0, 1, &nullView); } -void TextureCacheD3D11::ApplyTexture() { - TexCacheEntry *entry = nextTexture_; - if (entry == nullptr) { - return; - } - nextTexture_ = nullptr; - - UpdateMaxSeenV(entry, gstate.isModeThrough()); - - 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()); - } - } - - // 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 { - ID3D11ShaderResourceView *textureView = DxView(entry); - if (textureView != lastBoundTexture) { - context_->PSSetShaderResources(0, 1, &textureView); - lastBoundTexture = textureView; - } - SamplerCacheKey key; - UpdateSamplingParams(*entry, key); - ID3D11SamplerState *state = samplerCache_.GetOrCreateSampler(device_, key); - context_->PSSetSamplers(0, 1, &state); - gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; - gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; - } -} - class TextureShaderApplierD3D11 { public: struct Pos { @@ -578,321 +446,6 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFra lastBoundTexture = INVALID_TEX; } -void TextureCacheD3D11::SetTexture(bool force) { -#ifdef DEBUG_TEXTURES - if (SetDebugTexture()) { - // A different texture was bound, let's rebind next time. - lastBoundTexture = INVALID_TEX; - return; - } -#endif - - if (force) { - lastBoundTexture = INVALID_TEX; - } - - u32 texaddr = gstate.getTextureAddress(0); - if (!Memory::IsValidAddress(texaddr)) { - // Bind a null texture and return. - ID3D11ShaderResourceView *srv = nullptr; - context_->PSSetShaderResources(0, 1, &srv); - lastBoundTexture = INVALID_TEX; - return; - } - - const u16 dim = gstate.getTextureDimension(0); - int w = gstate.getTextureWidth(0); - int h = gstate.getTextureHeight(0); - - GETextureFormat format = gstate.getTextureFormat(); - if (format >= 11) { - ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format); - // TODO: Better assumption? - format = GE_TFMT_5650; - } - bool hasClut = gstate.isTextureFormatIndexed(); - - // Ignore uncached/kernel when caching. - u32 cluthash; - if (hasClut) { - if (clutLastFormat_ != gstate.clutformat) { - // We update here because the clut format can be specified after the load. - UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); - } - cluthash = GetCurrentClutHash() ^ gstate.clutformat; - } else { - cluthash = 0; - } - u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash); - - int bufw = GetTextureBufw(0, texaddr, format); - u8 maxLevel = gstate.getTextureMaxLevel(); - - u32 texhash = MiniHash((const u32 *)Memory::GetPointer(texaddr)); - - TexCache::iterator iter = cache.find(cachekey); - TexCacheEntry *entry = NULL; - gstate_c.needShaderTexClamp = false; - gstate_c.bgraTexture = true; - gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE; - bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; - - if (iter != cache.end()) { - entry = &iter->second; - // Validate the texture still matches the cache entry. - bool match = entry->Matches(dim, format, maxLevel); - const char *reason = "different params"; - - // Check for FBO - slow! - if (entry->framebuffer) { - if (match) { - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - - SetTextureFramebuffer(entry, entry->framebuffer); - return; - } else { - // Make sure we re-evaluate framebuffers. - DetachFramebuffer(entry, texaddr, entry->framebuffer); - reason = "detached framebuf"; - match = false; - } - } - - bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE; - - // First let's see if another texture with the same address had a hashfail. - if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) { - // Always rehash in this case, if one changed the rest all probably did. - rehash = true; - entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK; - } else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) { - // Okay, just some parameter change - the data didn't change, no need to rehash. - rehash = false; - } - - if (match) { - if (entry->lastFrame != gpuStats.numFlips) { - u32 diff = gpuStats.numFlips - entry->lastFrame; - entry->numFrames++; - - if (entry->framesUntilNextFullHash < diff) { - // Exponential backoff up to 512 frames. Textures are often reused. - if (entry->numFrames > 32) { - // Also, try to add some "randomness" to avoid rehashing several textures the same frame. - entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + ((intptr_t)entry->texturePtr & 15); - } else { - entry->framesUntilNextFullHash = entry->numFrames; - } - rehash = true; - } else { - entry->framesUntilNextFullHash -= diff; - } - } - - // If it's not huge or has been invalidated many times, recheck the whole texture. - if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) { - entry->invalidHint = 0; - rehash = true; - } - - if (texhash != entry->hash) { - match = false; - } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - rehash = false; - } - } - - if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) { - if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { - // INFO_LOG(G3D, "Reloading texture to do the scaling we skipped.."); - match = false; - reason = "scaling"; - } - } - - if (match) { - // TODO: Mark the entry reliable if it's been safe for long enough? - //got one! - if (entry->texturePtr != lastBoundTexture) { - 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 { - nextChangeReason_ = reason; - nextNeedsChange_ = true; - } - } else { - VERBOSE_LOG(G3D, "No texture in cache, decoding..."); - TexCacheEntry entryNew = { 0 }; - cache[cachekey] = entryNew; - - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - - entry = &cache[cachekey]; - if (g_Config.bTextureBackoffCache) { - entry->status = TexCacheEntry::STATUS_HASHING; - } else { - entry->status = TexCacheEntry::STATUS_UNRELIABLE; - } - - 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->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->cluthash = cluthash; - - gstate_c.curTextureWidth = w; - gstate_c.curTextureHeight = h; - - // Before we go reading the texture from memory, let's check for render-to-texture. - // We must do this early so we have the right w/h. - entry->framebuffer = 0; - for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { - auto framebuffer = fbCache_[i]; - AttachFramebuffer(entry, framebuffer->fb_address, framebuffer); - } - - // If we ended up with a framebuffer, attach it - no texture decoding needed. - if (entry->framebuffer) { - SetTextureFramebuffer(entry, entry->framebuffer); - } - - nextTexture_ = entry; - nextNeedsRehash_ = entry->framebuffer == nullptr; - // We still need to rebuild, to allocate a texture. But we'll bail early. - nextNeedsRebuild_ = true; -} - -bool TextureCacheD3D11::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 TextureCacheD3D11::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 && (entry->status & TexCacheEntry::STATUS_IS_SCALED) == 0) { - // 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); - entry->status &= ~TexCacheEntry::STATUS_IS_SCALED; - } - } - // 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 TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry, bool replaceImages) { entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; @@ -951,10 +504,10 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry, bool replaceIma scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } - u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0; + 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); + ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); if (replaced.GetSize(0, w, h)) { if (replaceImages) { // Since we're replacing the texture, we can't replace the image inside. @@ -1041,7 +594,7 @@ DXGI_FORMAT TextureCacheD3D11::GetDestFormat(GETextureFormat format, GEPaletteFo } } -TextureCacheD3D11::TexCacheEntry::Status TextureCacheD3D11::CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h) { +TexCacheEntry::Status TextureCacheD3D11::CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h) { CheckAlphaResult res; switch (dstFmt) { case DXGI_FORMAT_B4G4R4A4_UNORM: @@ -1142,8 +695,8 @@ void TextureCacheD3D11::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture & u32 *pixelData; int decPitch; if (scaleFactor > 1) { - tmpTexBufRearrange.resize(std::max(bufw, w) * h); - pixelData = tmpTexBufRearrange.data(); + tmpTexBufRearrange_.resize(std::max(bufw, w) * h); + pixelData = tmpTexBufRearrange_.data(); // We want to end up with a neatly packed texture for scaling. decPitch = w * bpp; mapData = new u32[(w * scaleFactor) * (h * scaleFactor)]; @@ -1187,7 +740,7 @@ void TextureCacheD3D11::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture & entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); } - if (replacer.Enabled()) { + if (replacer_.Enabled()) { ReplacedTextureDecodeInfo replacedInfo; replacedInfo.cachekey = entry.CacheKey(); replacedInfo.hash = entry.fullhash; @@ -1197,7 +750,7 @@ void TextureCacheD3D11::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture & replacedInfo.scaleFactor = scaleFactor; replacedInfo.fmt = FromD3D11Format(dstFmt); - replacer.NotifyTextureDecoded(replacedInfo, pixelData, decPitch, level, w, h); + replacer_.NotifyTextureDecoded(replacedInfo, pixelData, decPitch, level, w, h); } } diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index 4c4f2e2d66..68b2156f1f 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -46,9 +46,6 @@ public: TextureCacheD3D11(Draw::DrawContext *draw); ~TextureCacheD3D11(); - void SetTexture(bool force = false); - - void Clear(bool delete_them); void StartFrame(); void SetFramebufferManager(FramebufferManagerD3D11 *fbManager); @@ -63,28 +60,24 @@ public: bool DecodeTexture(u8 *output, const GPUgstate &state); void ForgetLastTexture() override; + void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override; void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key); - void ApplyTexture(); - protected: + void BindTexture(TexCacheEntry *entry) override; void Unbind() override; + void ReleaseTexture(TexCacheEntry *entry) override; private: - void Decimate(); // Run this once per frame to get rid of old textures. - void DeleteTexture(TexCache::iterator it); void UpdateSamplingParams(TexCacheEntry &entry, SamplerCacheKey &key); void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int maxLevel, bool replaceImages, int scaleFactor, u32 dstFmt); DXGI_FORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; TexCacheEntry::Status CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h); - u32 GetCurrentClutHash(); - void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple); - void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); + void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; - 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); + void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override; + void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override; ID3D11Device *device_; ID3D11DeviceContext *context_; @@ -95,25 +88,11 @@ private: ID3D11ShaderResourceView *DxView(TexCacheEntry *entry) { return (ID3D11ShaderResourceView *)entry->textureView; } - void ReleaseTexture(TexCacheEntry *entry) { - ID3D11Texture2D *texture = (ID3D11Texture2D *)entry->texturePtr; - ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)entry->textureView; - if (texture) { - texture->Release(); - entry->texturePtr = nullptr; - } - if (view) { - view->Release(); - entry->textureView = nullptr; - } - } TextureScalerD3D11 scaler; SamplerCacheD3D11 samplerCache_; - u32 clutHash_; - ID3D11ShaderResourceView *lastBoundTexture; int decimationCounter_; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index db6b5924de..ed956e6429 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -43,20 +43,6 @@ namespace DX9 { #define INVALID_TEX (LPDIRECT3DTEXTURE9)(-1) -// If a texture hasn't been seen for this many frames, get rid of it. -#define TEXTURE_KILL_AGE 200 -#define TEXTURE_KILL_AGE_LOWMEM 60 -// Not used in lowmem mode. -#define TEXTURE_SECOND_KILL_AGE 100 - -// Try to be prime to other decimation intervals. -#define TEXCACHE_DECIMATION_INTERVAL 13 - -#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame - -#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in VRAM -#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024 - static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, { 0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, @@ -66,7 +52,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { TextureCacheDX9::TextureCacheDX9(Draw::DrawContext *draw) : TextureCacheCommon(draw) { lastBoundTexture = INVALID_TEX; - decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; + isBgraBackend_ = true; D3DCAPS9 pCaps; ZeroMemory(&pCaps, sizeof(pCaps)); @@ -98,39 +84,13 @@ void TextureCacheDX9::SetFramebufferManager(FramebufferManagerDX9 *fbManager) { framebufferManager_ = fbManager; } -void TextureCacheDX9::Clear(bool delete_them) { - pD3Ddevice->SetTexture(0, NULL); - lastBoundTexture = INVALID_TEX; - if (delete_them) { - for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter) { - DEBUG_LOG(G3D, "Deleting texture %p", iter->second.texturePtr); - ReleaseTexture(&iter->second); - } - for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ++iter) { - DEBUG_LOG(G3D, "Deleting texture %p", iter->second.texturePtr); - ReleaseTexture(&iter->second); - } +void TextureCacheDX9::ReleaseTexture(TexCacheEntry *entry) { + DEBUG_LOG(G3D, "Deleting texture %p", entry->texturePtr); + LPDIRECT3DTEXTURE9 &texture = DxTex(entry); + if (texture) { + texture->Release(); + texture = nullptr; } - if (cache.size() + secondCache.size()) { - INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache.size() + secondCache.size())); - cache.clear(); - secondCache.clear(); - cacheSizeEstimate_ = 0; - secondCacheSizeEstimate_ = 0; - } - fbTexInfo_.clear(); - videos_.clear(); -} - -void TextureCacheDX9::DeleteTexture(TexCache::iterator it) { - ReleaseTexture(&it->second); - auto fbInfo = fbTexInfo_.find(it->first); - if (fbInfo != fbTexInfo_.end()) { - fbTexInfo_.erase(fbInfo); - } - - cacheSizeEstimate_ -= EstimateTexMemoryUsage(&it->second); - cache.erase(it); } void TextureCacheDX9::ForgetLastTexture() { @@ -138,48 +98,10 @@ void TextureCacheDX9::ForgetLastTexture() { gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); } -// Removes old textures. -void TextureCacheDX9::Decimate() { - if (--decimationCounter_ <= 0) { - decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; - } else { - return; - } - - if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) { - const u32 had = cacheSizeEstimate_; - - pD3Ddevice->SetTexture(0, NULL); +void TextureCacheDX9::InvalidateLastTexture(TexCacheEntry *entry) { + if (!entry || entry->texturePtr == lastBoundTexture) { lastBoundTexture = INVALID_TEX; - int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE; - for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) { - if (iter->second.lastFrame + killAge < gpuStats.numFlips) { - DeleteTexture(iter++); - } else { - ++iter; - } - } - - VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_); } - - if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) { - const u32 had = secondCacheSizeEstimate_; - - for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ) { - // In low memory mode, we kill them all. - if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_KILL_AGE < gpuStats.numFlips) { - ReleaseTexture(&iter->second); - secondCache.erase(iter++); - } else { - ++iter; - } - } - - VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_); - } - - DecimateVideos(); } D3DFORMAT getClutDestFormat(GEPaletteFormat format) { @@ -311,10 +233,6 @@ void TextureCacheDX9::StartFrame() { } -static inline u32 MiniHash(const u32 *ptr) { - return ptr[0]; -} - void TextureCacheDX9::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) { const u32 clutBaseBytes = clutBase * (clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16)); // Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier. @@ -348,72 +266,19 @@ void TextureCacheDX9::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase clutLastFormat_ = gstate.clutformat; } -inline u32 TextureCacheDX9::GetCurrentClutHash() { - return clutHash_; +void TextureCacheDX9::BindTexture(TexCacheEntry *entry) { + LPDIRECT3DTEXTURE9 texture = DxTex(entry); + if (texture != lastBoundTexture) { + pD3Ddevice->SetTexture(0, texture); + lastBoundTexture = texture; + } + UpdateSamplingParams(*entry, false); } void TextureCacheDX9::Unbind() { pD3Ddevice->SetTexture(0, NULL); } -void TextureCacheDX9::ApplyTexture() { - TexCacheEntry *entry = nextTexture_; - if (entry == nullptr) { - return; - } - nextTexture_ = nullptr; - - UpdateMaxSeenV(entry, gstate.isModeThrough()); - - 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()); - } - } - - // 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; - } -} - class TextureShaderApplierDX9 { public: struct Pos { @@ -591,324 +456,6 @@ void TextureCacheDX9::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFrame lastBoundTexture = INVALID_TEX; } -void TextureCacheDX9::SetTexture(bool force) { -#ifdef DEBUG_TEXTURES - if (SetDebugTexture()) { - // A different texture was bound, let's rebind next time. - lastBoundTexture = INVALID_TEX; - return; - } -#endif - - if (force) { - lastBoundTexture = INVALID_TEX; - } - - u8 level = 0; - if (IsFakeMipmapChange()) - level = (gstate.texlevel >> 20) & 0xF; - u32 texaddr = gstate.getTextureAddress(level); - if (!Memory::IsValidAddress(texaddr)) { - // Bind a null texture and return. - pD3Ddevice->SetTexture(0, NULL); - lastBoundTexture = INVALID_TEX; - return; - } - - const u16 dim = gstate.getTextureDimension(level); - int w = gstate.getTextureWidth(level); - int h = gstate.getTextureHeight(level); - - GETextureFormat format = gstate.getTextureFormat(); - if (format >= 11) { - ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format); - // TODO: Better assumption? - format = GE_TFMT_5650; - } - bool hasClut = gstate.isTextureFormatIndexed(); - - // Ignore uncached/kernel when caching. - u32 cluthash; - if (hasClut) { - if (clutLastFormat_ != gstate.clutformat) { - // We update here because the clut format can be specified after the load. - UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); - } - cluthash = GetCurrentClutHash() ^ gstate.clutformat; - } else { - cluthash = 0; - } - u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash); - - int bufw = GetTextureBufw(0, texaddr, format); - u8 maxLevel = gstate.getTextureMaxLevel(); - - u32 texhash = MiniHash((const u32 *)Memory::GetPointer(texaddr)); - - TexCache::iterator iter = cache.find(cachekey); - TexCacheEntry *entry = NULL; - gstate_c.needShaderTexClamp = false; - gstate_c.bgraTexture = true; - gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE; - bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; - - if (iter != cache.end()) { - entry = &iter->second; - // Validate the texture still matches the cache entry. - bool match = entry->Matches(dim, format, maxLevel); - const char *reason = "different params"; - - // Check for FBO - slow! - if (entry->framebuffer) { - if (match) { - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - - SetTextureFramebuffer(entry, entry->framebuffer); - return; - } else { - // Make sure we re-evaluate framebuffers. - DetachFramebuffer(entry, texaddr, entry->framebuffer); - reason = "detached framebuf"; - match = false; - } - } - - bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE; - - // First let's see if another texture with the same address had a hashfail. - if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) { - // Always rehash in this case, if one changed the rest all probably did. - rehash = true; - entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK; - } else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) { - // Okay, just some parameter change - the data didn't change, no need to rehash. - rehash = false; - } - - if (match) { - if (entry->lastFrame != gpuStats.numFlips) { - u32 diff = gpuStats.numFlips - entry->lastFrame; - entry->numFrames++; - - if (entry->framesUntilNextFullHash < diff) { - // Exponential backoff up to 512 frames. Textures are often reused. - if (entry->numFrames > 32) { - // Also, try to add some "randomness" to avoid rehashing several textures the same frame. - entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + ((intptr_t)entry->texturePtr & 15); - } else { - entry->framesUntilNextFullHash = entry->numFrames; - } - rehash = true; - } else { - entry->framesUntilNextFullHash -= diff; - } - } - - // If it's not huge or has been invalidated many times, recheck the whole texture. - if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) { - entry->invalidHint = 0; - rehash = true; - } - - if (texhash != entry->hash) { - match = false; - } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - rehash = false; - } - } - - if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) { - if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { - // INFO_LOG(G3D, "Reloading texture to do the scaling we skipped.."); - match = false; - reason = "scaling"; - } - } - - if (match) { - // TODO: Mark the entry reliable if it's been safe for long enough? - //got one! - if (entry->texturePtr != lastBoundTexture) { - 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 { - nextChangeReason_ = reason; - nextNeedsChange_ = true; - } - } else { - VERBOSE_LOG(G3D, "No texture in cache, decoding..."); - TexCacheEntry entryNew = {0}; - cache[cachekey] = entryNew; - - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - - entry = &cache[cachekey]; - if (g_Config.bTextureBackoffCache) { - entry->status = TexCacheEntry::STATUS_HASHING; - } else { - entry->status = TexCacheEntry::STATUS_UNRELIABLE; - } - - 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->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->cluthash = cluthash; - - gstate_c.curTextureWidth = w; - gstate_c.curTextureHeight = h; - - // Before we go reading the texture from memory, let's check for render-to-texture. - // We must do this early so we have the right w/h. - entry->framebuffer = 0; - for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { - auto framebuffer = fbCache_[i]; - AttachFramebuffer(entry, framebuffer->fb_address, framebuffer); - } - - // If we ended up with a framebuffer, attach it - no texture decoding needed. - if (entry->framebuffer) { - SetTextureFramebuffer(entry, entry->framebuffer); - } - - nextTexture_ = entry; - nextNeedsRehash_ = entry->framebuffer == nullptr; - // We still need to rebuild, to allocate a texture. But we'll bail early. - 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 && (entry->status & TexCacheEntry::STATUS_IS_SCALED) == 0) { - // 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); - entry->status &= ~TexCacheEntry::STATUS_IS_SCALED; - } - } - // 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; @@ -966,10 +513,10 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry, bool replaceImage scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } - u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0; + 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); + ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); if (replaced.GetSize(0, w, h)) { if (replaceImages) { // Since we're replacing the texture, we can't replace the image inside. @@ -1058,7 +605,7 @@ D3DFORMAT TextureCacheDX9::GetDestFormat(GETextureFormat format, GEPaletteFormat } } -TextureCacheDX9::TexCacheEntry::Status TextureCacheDX9::CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h) { +TexCacheEntry::Status TextureCacheDX9::CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h) { CheckAlphaResult res; switch (dstFmt) { case D3DFMT_A4R4G4B4: @@ -1154,8 +701,8 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re u32 *pixelData = (u32 *)rect.pBits; int decPitch = rect.Pitch; if (scaleFactor > 1) { - tmpTexBufRearrange.resize(std::max(bufw, w) * h); - pixelData = tmpTexBufRearrange.data(); + tmpTexBufRearrange_.resize(std::max(bufw, w) * h); + pixelData = tmpTexBufRearrange_.data(); // We want to end up with a neatly packed texture for scaling. decPitch = w * bpp; } @@ -1191,7 +738,7 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); } - if (replacer.Enabled()) { + if (replacer_.Enabled()) { ReplacedTextureDecodeInfo replacedInfo; replacedInfo.cachekey = entry.CacheKey(); replacedInfo.hash = entry.fullhash; @@ -1201,7 +748,7 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re replacedInfo.scaleFactor = scaleFactor; replacedInfo.fmt = FromD3D9Format(dstFmt); - replacer.NotifyTextureDecoded(replacedInfo, pixelData, decPitch, level, w, h); + replacer_.NotifyTextureDecoded(replacedInfo, pixelData, decPitch, level, w, h); } } diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index 2c5a8a8401..714b467315 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -40,9 +40,6 @@ public: TextureCacheDX9(Draw::DrawContext *draw); ~TextureCacheDX9(); - void SetTexture(bool force = false); - - void Clear(bool delete_them); void StartFrame(); void SetFramebufferManager(FramebufferManagerDX9 *fbManager); @@ -57,44 +54,31 @@ public: bool DecodeTexture(u8 *output, const GPUgstate &state); void ForgetLastTexture() override; + void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override; void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight); - void ApplyTexture(); - protected: + void BindTexture(TexCacheEntry *entry) override; void Unbind() override; + void ReleaseTexture(TexCacheEntry *entry) override; private: - void Decimate(); // Run this once per frame to get rid of old textures. - void DeleteTexture(TexCache::iterator it); void UpdateSamplingParams(TexCacheEntry &entry, bool force); void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int maxLevel, bool replaceImages, int scaleFactor, u32 dstFmt); D3DFORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; TexCacheEntry::Status CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h); - u32 GetCurrentClutHash(); - void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple); - void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); + void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; - 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); + void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override; + void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override; LPDIRECT3DTEXTURE9 &DxTex(TexCacheEntry *entry) { return *(LPDIRECT3DTEXTURE9 *)&entry->texturePtr; } - void ReleaseTexture(TexCacheEntry *entry) { - LPDIRECT3DTEXTURE9 &texture = DxTex(entry); - if (texture) { - texture->Release(); - texture = nullptr; - } - } TextureScalerDX9 scaler; - u32 clutHash_; - LPDIRECT3DVERTEXDECLARATION9 pFramebufferVertexDecl; LPDIRECT3DTEXTURE9 lastBoundTexture; diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 58a4a1ea43..506a341e29 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -43,33 +43,16 @@ #include #endif -// If a texture hasn't been seen for this many frames, get rid of it. -#define TEXTURE_KILL_AGE 200 -#define TEXTURE_KILL_AGE_LOWMEM 60 -// Not used in lowmem mode. -#define TEXTURE_SECOND_KILL_AGE 100 - -// Try to be prime to other decimation intervals. -#define TEXCACHE_DECIMATION_INTERVAL 13 - -#define TEXCACHE_NAME_CACHE_SIZE 16 - -#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame - -#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in GL -#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024 - #ifndef GL_UNPACK_ROW_LENGTH #define GL_UNPACK_ROW_LENGTH 0x0CF2 #endif -#define INVALID_TEX -1 +#define TEXCACHE_NAME_CACHE_SIZE 16 TextureCacheGLES::TextureCacheGLES(Draw::DrawContext *draw) : TextureCacheCommon(draw) { timesInvalidatedAllThisFrame_ = 0; lastBoundTexture = INVALID_TEX; - decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropyLevel); SetupTextureDecoder(); @@ -86,88 +69,19 @@ void TextureCacheGLES::SetFramebufferManager(FramebufferManagerGLES *fbManager) framebufferManager_ = fbManager; } +void TextureCacheGLES::ReleaseTexture(TexCacheEntry *entry) { + DEBUG_LOG(G3D, "Deleting texture %i", entry->textureName); + glDeleteTextures(1, &entry->textureName); +} + void TextureCacheGLES::Clear(bool delete_them) { - glBindTexture(GL_TEXTURE_2D, 0); - lastBoundTexture = INVALID_TEX; + TextureCacheCommon::Clear(delete_them); if (delete_them) { - for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter) { - DEBUG_LOG(G3D, "Deleting texture %i", iter->second.textureName); - glDeleteTextures(1, &iter->second.textureName); - } - for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ++iter) { - DEBUG_LOG(G3D, "Deleting texture %i", iter->second.textureName); - glDeleteTextures(1, &iter->second.textureName); - } if (!nameCache_.empty()) { glDeleteTextures((GLsizei)nameCache_.size(), &nameCache_[0]); nameCache_.clear(); } } - if (cache.size() + secondCache.size()) { - INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache.size() + secondCache.size())); - cache.clear(); - secondCache.clear(); - cacheSizeEstimate_ = 0; - secondCacheSizeEstimate_ = 0; - } - fbTexInfo_.clear(); - videos_.clear(); -} - -void TextureCacheGLES::DeleteTexture(TexCache::iterator it) { - glDeleteTextures(1, &it->second.textureName); - auto fbInfo = fbTexInfo_.find(it->first); - if (fbInfo != fbTexInfo_.end()) { - fbTexInfo_.erase(fbInfo); - } - - cacheSizeEstimate_ -= EstimateTexMemoryUsage(&it->second); - cache.erase(it); -} - -// Removes old textures. -void TextureCacheGLES::Decimate() { - if (--decimationCounter_ <= 0) { - decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; - } else { - return; - } - - if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) { - const u32 had = cacheSizeEstimate_; - - glBindTexture(GL_TEXTURE_2D, 0); - lastBoundTexture = INVALID_TEX; - int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE; - for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) { - if (iter->second.lastFrame + killAge < gpuStats.numFlips) { - DeleteTexture(iter++); - } else { - ++iter; - } - } - - VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_); - } - - if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) { - const u32 had = secondCacheSizeEstimate_; - - for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ) { - // In low memory mode, we kill them all. - if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.numFlips) { - glDeleteTextures(1, &iter->second.textureName); - secondCacheSizeEstimate_ -= EstimateTexMemoryUsage(&iter->second); - secondCache.erase(iter++); - } else { - ++iter; - } - } - - VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_); - } - - DecimateVideos(); } GLenum getClutDestFormat(GEPaletteFormat format) { @@ -307,7 +221,7 @@ static void ConvertColors(void *dstBuf, const void *srcBuf, GLuint dstFmt, int n } void TextureCacheGLES::StartFrame() { - lastBoundTexture = INVALID_TEX; + InvalidateLastTexture(); timesInvalidatedAllThisFrame_ = 0; if (texelsScaledThisFrame_) { @@ -322,10 +236,6 @@ void TextureCacheGLES::StartFrame() { } } -static inline u32 MiniHash(const u32 *ptr) { - return ptr[0]; -} - void TextureCacheGLES::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) { const u32 clutBaseBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutBase * sizeof(u32)) : (clutBase * sizeof(u16)); // Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier. @@ -367,10 +277,6 @@ void TextureCacheGLES::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBas clutLastFormat_ = gstate.clutformat; } -inline u32 TextureCacheGLES::GetCurrentClutHash() { - return clutHash_; -} - // #define DEBUG_TEXTURES #ifdef DEBUG_TEXTURES @@ -413,64 +319,16 @@ bool SetDebugTexture() { } #endif -void TextureCacheGLES::Unbind() { - glBindTexture(GL_TEXTURE_2D, 0); +void TextureCacheGLES::BindTexture(TexCacheEntry *entry) { + if (entry->textureName != lastBoundTexture) { + glBindTexture(GL_TEXTURE_2D, entry->textureName); + lastBoundTexture = entry->textureName; + } + UpdateSamplingParams(*entry, false); } -void TextureCacheGLES::ApplyTexture() { - TexCacheEntry *entry = nextTexture_; - if (entry == nullptr) { - return; - } - nextTexture_ = nullptr; - - UpdateMaxSeenV(entry, gstate.isModeThrough()); - - 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()); - } - } - - // 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 TextureCacheGLES::Unbind() { + glBindTexture(GL_TEXTURE_2D, 0); } class TextureShaderApplier { @@ -673,259 +531,6 @@ GLenum ToGLESFormat(ReplacedTextureFormat fmt) { } } -void TextureCacheGLES::SetTexture(bool force) { -#ifdef DEBUG_TEXTURES - if (SetDebugTexture()) { - // A different texture was bound, let's rebind next time. - lastBoundTexture = INVALID_TEX; - return; - } -#endif - - if (force) { - lastBoundTexture = INVALID_TEX; - } - - u8 level = 0; - if (IsFakeMipmapChange()) - level = (gstate.texlevel >> 20) & 0xF; - u32 texaddr = gstate.getTextureAddress(level); - if (!Memory::IsValidAddress(texaddr)) { - // Bind a null texture and return. - glBindTexture(GL_TEXTURE_2D, 0); - lastBoundTexture = INVALID_TEX; - return; - } - - const u16 dim = gstate.getTextureDimension(level); - int w = gstate.getTextureWidth(level); - int h = gstate.getTextureHeight(level); - - GETextureFormat format = gstate.getTextureFormat(); - if (format >= 11) { - ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format); - // TODO: Better assumption? - format = GE_TFMT_5650; - } - bool hasClut = gstate.isTextureFormatIndexed(); - - // Ignore uncached/kernel when caching. - u32 cluthash; - if (hasClut) { - if (clutLastFormat_ != gstate.clutformat) { - // We update here because the clut format can be specified after the load. - UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); - } - cluthash = GetCurrentClutHash() ^ gstate.clutformat; - } else { - cluthash = 0; - } - u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash); - - int bufw = GetTextureBufw(0, texaddr, format); - u8 maxLevel = gstate.getTextureMaxLevel(); - - u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr)); - - TexCache::iterator iter = cache.find(cachekey); - TexCacheEntry *entry = NULL; - gstate_c.needShaderTexClamp = false; - gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE; - - if (iter != cache.end()) { - entry = &iter->second; - // Validate the texture still matches the cache entry. - bool match = entry->Matches(dim, format, maxLevel); - const char *reason = "different params"; - - // Check for FBO - slow! - if (entry->framebuffer) { - if (match) { - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - - SetTextureFramebuffer(entry, entry->framebuffer); - return; - } else { - // Make sure we re-evaluate framebuffers. - DetachFramebuffer(entry, texaddr, entry->framebuffer); - reason = "detached framebuf"; - match = false; - } - } - - bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE; - - // First let's see if another texture with the same address had a hashfail. - if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) { - // Always rehash in this case, if one changed the rest all probably did. - rehash = true; - entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK; - } else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) { - // Okay, just some parameter change - the data didn't change, no need to rehash. - rehash = false; - } - - if (match) { - if (entry->lastFrame != gpuStats.numFlips) { - u32 diff = gpuStats.numFlips - entry->lastFrame; - entry->numFrames++; - - if (entry->framesUntilNextFullHash < diff) { - // Exponential backoff up to 512 frames. Textures are often reused. - if (entry->numFrames > 32) { - // Also, try to add some "randomness" to avoid rehashing several textures the same frame. - entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + (entry->textureName & 15); - } else { - entry->framesUntilNextFullHash = entry->numFrames; - } - rehash = true; - } else { - entry->framesUntilNextFullHash -= diff; - } - } - - // If it's not huge or has been invalidated many times, recheck the whole texture. - if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) { - entry->invalidHint = 0; - rehash = true; - } - - if (texhash != entry->hash) { - match = false; - } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - rehash = false; - } - } - - if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) { - if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { - // INFO_LOG(G3D, "Reloading texture to do the scaling we skipped.."); - match = false; - reason = "scaling"; - } - } - - if (match) { - // TODO: Mark the entry reliable if it's been safe for long enough? - //got one! - if (entry->textureName != lastBoundTexture) { - 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 { - nextChangeReason_ = reason; - nextNeedsChange_ = true; - } - } else { - VERBOSE_LOG(G3D, "No texture in cache, decoding..."); - TexCacheEntry entryNew = {0}; - cache[cachekey] = entryNew; - - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - - entry = &cache[cachekey]; - if (g_Config.bTextureBackoffCache) { - entry->status = TexCacheEntry::STATUS_HASHING; - } else { - entry->status = TexCacheEntry::STATUS_UNRELIABLE; - } - - 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->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->cluthash = cluthash; - - gstate_c.curTextureWidth = w; - gstate_c.curTextureHeight = h; - - // Before we go reading the texture from memory, let's check for render-to-texture. - // We must do this early so we have the right w/h. - entry->framebuffer = 0; - for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { - auto framebuffer = fbCache_[i]; - AttachFramebuffer(entry, framebuffer->fb_address, framebuffer); - } - - // If we ended up with a framebuffer, attach it - no texture decoding needed. - if (entry->framebuffer) { - SetTextureFramebuffer(entry, entry->framebuffer); - } - - nextTexture_ = entry; - nextNeedsRehash_ = entry->framebuffer == nullptr; - // We still need to rebuild, to allocate a texture. But we'll bail early. - nextNeedsRebuild_= true; -} - -bool TextureCacheGLES::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 && (entry->status & TexCacheEntry::STATUS_IS_SCALED) == 0) { - // 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); - entry->status &= ~TexCacheEntry::STATUS_IS_SCALED; - } - } - // 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 TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImages) { entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; @@ -985,10 +590,10 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImag scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } - u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0; + 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); + ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); if (replaced.GetSize(0, w, h)) { if (replaceImages) { // Since we're replacing the texture, we can't replace the image inside. @@ -1159,9 +764,9 @@ void *TextureCacheGLES::DecodeTextureLevelOld(GETextureFormat format, GEPaletteF decPitch = bufw * pixelSize; } - tmpTexBufRearrange.resize(std::max(w, bufw) * h); - if (DecodeTextureLevel((u8 *)tmpTexBufRearrange.data(), decPitch, format, clutformat, texaddr, level, bufw, true, UseBGRA8888())) { - finalBuf = tmpTexBufRearrange.data(); + tmpTexBufRearrange_.resize(std::max(w, bufw) * h); + if (DecodeTextureLevel((u8 *)tmpTexBufRearrange_.data(), decPitch, format, clutformat, texaddr, level, bufw, true, UseBGRA8888())) { + finalBuf = tmpTexBufRearrange_.data(); } else { finalBuf = nullptr; } @@ -1173,7 +778,7 @@ void *TextureCacheGLES::DecodeTextureLevelOld(GETextureFormat format, GEPaletteF return finalBuf; } -TextureCacheGLES::TexCacheEntry::Status TextureCacheGLES::CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h) { +TexCacheEntry::Status TextureCacheGLES::CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h) { CheckAlphaResult res; switch (dstFmt) { case GL_UNSIGNED_SHORT_4_4_4_4: @@ -1209,10 +814,10 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r if (replaced.GetSize(level, w, h)) { PROFILE_THIS_SCOPE("replacetex"); - tmpTexBufRearrange.resize(w * h); + tmpTexBufRearrange_.resize(w * h); int bpp = replaced.Format(level) == ReplacedTextureFormat::F_8888 ? 4 : 2; - replaced.Load(level, tmpTexBufRearrange.data(), bpp * w); - pixelData = tmpTexBufRearrange.data(); + replaced.Load(level, tmpTexBufRearrange_.data(), bpp * w); + pixelData = tmpTexBufRearrange_.data(); dstFmt = ToGLESFormat(replaced.Format(level)); @@ -1249,7 +854,7 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); } - if (replacer.Enabled()) { + if (replacer_.Enabled()) { ReplacedTextureDecodeInfo replacedInfo; replacedInfo.cachekey = entry.CacheKey(); replacedInfo.hash = entry.fullhash; @@ -1260,7 +865,7 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r replacedInfo.fmt = FromGLESFormat(dstFmt, useBGRA); int bpp = dstFmt == GL_UNSIGNED_BYTE ? 4 : 2; - replacer.NotifyTextureDecoded(replacedInfo, pixelData, (useUnpack ? bufw : w) * bpp, level, w, h); + replacer_.NotifyTextureDecoded(replacedInfo, pixelData, (useUnpack ? bufw : w) * bpp, level, w, h); } } diff --git a/GPU/GLES/TextureCacheGLES.h b/GPU/GLES/TextureCacheGLES.h index e3c031b600..6b41125420 100644 --- a/GPU/GLES/TextureCacheGLES.h +++ b/GPU/GLES/TextureCacheGLES.h @@ -46,8 +46,6 @@ public: TextureCacheGLES(Draw::DrawContext *draw); ~TextureCacheGLES(); - void SetTexture(bool force = false); - void Clear(bool delete_them); void StartFrame(); @@ -63,9 +61,14 @@ public: } void ForgetLastTexture() override { - lastBoundTexture = -1; + lastBoundTexture = INVALID_TEX; gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); } + void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override { + if (!entry || entry->textureName == lastBoundTexture) { + lastBoundTexture = INVALID_TEX; + } + } u32 AllocTextureName(); @@ -74,32 +77,26 @@ public: void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight); - void ApplyTexture(); - protected: + void BindTexture(TexCacheEntry *entry) override; void Unbind() override; + void ReleaseTexture(TexCacheEntry *entry) override; private: - void Decimate(); // Run this once per frame to get rid of old textures. - void DeleteTexture(TexCache::iterator it); void UpdateSamplingParams(TexCacheEntry &entry, bool force); void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, bool replaceImages, int scaleFactor, GLenum dstFmt); GLenum GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; void *DecodeTextureLevelOld(GETextureFormat format, GEPaletteFormat clutformat, int level, GLenum dstFmt, int scaleFactor, int *bufw = 0); TexCacheEntry::Status CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h); - u32 GetCurrentClutHash(); - void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple); - void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer); + void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; + void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override; - bool HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete); - void BuildTexture(TexCacheEntry *const entry, bool replaceImages); + void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override; std::vector nameCache_; TextureScalerGLES scaler; - u32 clutHash_; - u32 lastBoundTexture; float maxAnisotropyLevel; @@ -107,6 +104,8 @@ private: DepalShaderCacheGLES *depalShaderCache_; ShaderManagerGLES *shaderManager_; DrawEngineGLES *drawEngine_; + + enum { INVALID_TEX = -1 }; }; GLenum getClutDestFormat(GEPaletteFormat format); diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index b6f445a95c..868bacb592 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -668,7 +668,8 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) { } if (textureNeedsApply) { - textureCache_->ApplyTexture(frame->pushUBO, imageView, sampler); + textureCache_->ApplyTexture(); + textureCache_->GetVulkanHandles(imageView, sampler); if (imageView == VK_NULL_HANDLE) imageView = nullTexture_->GetImageView(); if (sampler == VK_NULL_HANDLE) @@ -768,7 +769,8 @@ 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(frame->pushUBO, imageView, sampler); + textureCache_->ApplyTexture(); + textureCache_->GetVulkanHandles(imageView, sampler); if (imageView == VK_NULL_HANDLE) imageView = nullTexture_->GetImageView(); if (sampler == VK_NULL_HANDLE) diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index f94d61f93f..cd77564093 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -123,7 +123,7 @@ public: void DirtyAllUBOs(); VulkanPushBuffer *GetPushBufferForTextureData() { - return frame_[curFrame_].pushUBO; + return frame_[curFrame_ & 1].pushUBO; } const DrawEngineVulkanStats &GetStats() const { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index f68623dcc3..a3d71a730d 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -1,3 +1,4 @@ + // Copyright (c) 2015- PPSSPP Project. // This program is free software: you can redistribute it and/or modify @@ -411,6 +412,7 @@ GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw) framebufferManagerVulkan_->Init(); framebufferManagerVulkan_->SetTextureCache(textureCacheVulkan_); framebufferManagerVulkan_->SetDrawEngine(&drawEngine_); + framebufferManagerVulkan_->SetShaderManager(shaderManagerVulkan_); textureCacheVulkan_->SetFramebufferManager(framebufferManagerVulkan_); textureCacheVulkan_->SetDepalShaderCache(&depalShaderCache_); textureCacheVulkan_->SetShaderManager(shaderManagerVulkan_); diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 4eb9d162c0..3272a06e56 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -48,22 +48,10 @@ #include #endif -// If a texture hasn't been seen for this many frames, get rid of it. -#define TEXTURE_KILL_AGE 200 -#define TEXTURE_KILL_AGE_LOWMEM 60 -// Not used in lowmem mode. -#define TEXTURE_SECOND_KILL_AGE 100 - -// Try to be prime to other decimation intervals. -#define TEXCACHE_DECIMATION_INTERVAL 13 - #define TEXCACHE_NAME_CACHE_SIZE 16 #define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame -#define TEXCACHE_MIN_PRESSURE (16 * 1024 * 1024) // Total in GL -#define TEXCACHE_SECOND_MIN_PRESSURE (4 * 1024 * 1024) - #define TEXCACHE_MIN_SLAB_SIZE (4 * 1024 * 1024) #define TEXCACHE_MAX_SLAB_SIZE (32 * 1024 * 1024) @@ -144,7 +132,6 @@ TextureCacheVulkan::TextureCacheVulkan(Draw::DrawContext *draw, VulkanContext *v texelsScaledThisFrame_(0) { timesInvalidatedAllThisFrame_ = 0; lastBoundTexture = nullptr; - decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; allocator_ = new VulkanDeviceAllocator(vulkan_, TEXCACHE_MIN_SLAB_SIZE, TEXCACHE_MAX_SLAB_SIZE); SetupTextureDecoder(); @@ -192,80 +179,9 @@ void TextureCacheVulkan::DeviceRestore(VulkanContext *vulkan) { samplerCache_.DeviceRestore(vulkan); } -void TextureCacheVulkan::Clear(bool delete_them) { - lastBoundTexture = nullptr; - for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter) { - DEBUG_LOG(G3D, "Deleting texture %p", iter->second.vkTex); - delete iter->second.vkTex; - } - for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ++iter) { - DEBUG_LOG(G3D, "Deleting texture %p", iter->second.vkTex); - delete iter->second.vkTex; - } - if (cache.size() + secondCache.size()) { - INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)(cache.size() + secondCache.size())); - cache.clear(); - secondCache.clear(); - cacheSizeEstimate_ = 0; - secondCacheSizeEstimate_ = 0; - } - fbTexInfo_.clear(); - videos_.clear(); -} - -void TextureCacheVulkan::DeleteTexture(TexCache::iterator it) { - delete it->second.vkTex; - auto fbInfo = fbTexInfo_.find(it->first); - if (fbInfo != fbTexInfo_.end()) { - fbTexInfo_.erase(fbInfo); - } - - cacheSizeEstimate_ -= EstimateTexMemoryUsage(&it->second); - cache.erase(it); -} - -// Removes old textures. -void TextureCacheVulkan::Decimate() { - if (--decimationCounter_ <= 0) { - decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; - } else { - return; - } - - if (cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) { - const u32 had = cacheSizeEstimate_; - - lastBoundTexture = nullptr; - int killAge = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE; - for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) { - if (iter->second.lastFrame + killAge < gpuStats.numFlips) { - DeleteTexture(iter++); - } else { - ++iter; - } - } - - VERBOSE_LOG(G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_); - } - - if (g_Config.bTextureSecondaryCache && secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE) { - const u32 had = secondCacheSizeEstimate_; - - for (TexCache::iterator iter = secondCache.begin(); iter != secondCache.end(); ) { - // In low memory mode, we kill them all. - if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.numFlips) { - delete iter->second.vkTex; - secondCacheSizeEstimate_ -= EstimateTexMemoryUsage(&iter->second); - secondCache.erase(iter++); - } else { - ++iter; - } - } - - VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_); - } - - DecimateVideos(); +void TextureCacheVulkan::ReleaseTexture(TexCacheEntry *entry) { + DEBUG_LOG(G3D, "Deleting texture %p", entry->vkTex); + delete entry->vkTex; } VkFormat getClutDestFormatVulkan(GEPaletteFormat format) { @@ -376,10 +292,6 @@ void TextureCacheVulkan::EndFrame() { } } -static inline u32 MiniHash(const u32 *ptr) { - return ptr[0]; -} - void TextureCacheVulkan::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) { const u32 clutBaseBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutBase * sizeof(u32)) : (clutBase * sizeof(u16)); // Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier. @@ -413,78 +325,25 @@ void TextureCacheVulkan::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutB clutLastFormat_ = gstate.clutformat; } -inline u32 TextureCacheVulkan::GetCurrentClutHash() { - return clutHash_; +void TextureCacheVulkan::BindTexture(TexCacheEntry *entry) { + if (!entry || !entry->vkTex) { + imageView_ = VK_NULL_HANDLE; + sampler_ = VK_NULL_HANDLE; + return; + } + + imageView_ = entry->vkTex->texture_->GetImageView(); + SamplerCacheKey key; + UpdateSamplingParams(*entry, key); + sampler_ = samplerCache_.GetOrCreateSampler(key); } void TextureCacheVulkan::Unbind() { + imageView_ = VK_NULL_HANDLE; + sampler_ = VK_NULL_HANDLE; } -void TextureCacheVulkan::ApplyTexture(VulkanPushBuffer *uploadBuffer, VkImageView &imageView, VkSampler &sampler) { - TexCacheEntry *entry = nextTexture_; - if (entry == nullptr) { - imageView = VK_NULL_HANDLE; - sampler = VK_NULL_HANDLE; - return; - } - 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); - } - 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(*entry, key); - sampler = samplerCache_.GetOrCreateSampler(key); - - 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; - } -} - -void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEntry *entry, VirtualFramebuffer *framebuffer, VkImageView &imageView, VkSampler &sampler) { +void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) { DepalShaderVulkan *depal = nullptr; const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); if ((entry->status & TexCacheEntry::STATUS_DEPALETTIZE) && !g_Config.bDisableSlowFramebufEffects) { @@ -558,7 +417,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEn uv[3] = UV(uvleft, uvtop); } - shaderManager_->DirtyLastShader(); + shaderManagerVulkan_->DirtyLastShader(); //depalFBO->EndPass(cmd); //depalFBO->TransitionToTexture(cmd); @@ -590,7 +449,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEn SamplerCacheKey key; UpdateSamplingParams(*entry, key); key.mipEnable = false; - sampler = samplerCache_.GetOrCreateSampler(key); + sampler_ = samplerCache_.GetOrCreateSampler(key); lastBoundTexture = nullptr; } @@ -614,314 +473,7 @@ VkFormat ToVulkanFormat(ReplacedTextureFormat fmt) { } } -void TextureCacheVulkan::SetTexture() { -#ifdef DEBUG_TEXTURES - if (SetDebugTexture()) { - // A different texture was bound, let's rebind next time. - lastBoundTexture = nullptr; - return; - } -#endif - - u8 level = 0; - if (IsFakeMipmapChange()) - level = (gstate.texlevel >> 20) & 0xF; - u32 texaddr = gstate.getTextureAddress(level); - if (!Memory::IsValidAddress(texaddr)) { - // Bind a null texture and return. - lastBoundTexture = nullptr; - return; - } - - const u16 dim = gstate.getTextureDimension(level); - int w = gstate.getTextureWidth(level); - int h = gstate.getTextureHeight(level); - if (texaddr == 0x04000000 && w == 2 && h == 2) { - // Nonsense bootup texture. Discard. - } - - GETextureFormat format = gstate.getTextureFormat(); - if (format >= 11) { - ERROR_LOG_REPORT(G3D, "Unknown texture format %i", format); - // TODO: Better assumption? - format = GE_TFMT_5650; - } - bool hasClut = gstate.isTextureFormatIndexed(); - - // Ignore uncached/kernel when caching. - u32 cluthash; - if (hasClut) { - if (clutLastFormat_ != gstate.clutformat) { - // We update here because the clut format can be specified after the load. - UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); - } - cluthash = GetCurrentClutHash() ^ gstate.clutformat; - } else { - cluthash = 0; - } - u64 cachekey = TexCacheEntry::CacheKey(texaddr, format, dim, cluthash); - - int bufw = GetTextureBufw(0, texaddr, format); - u8 maxLevel = gstate.getTextureMaxLevel(); - - u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr)); - - TexCache::iterator iter = cache.find(cachekey); - TexCacheEntry *entry = NULL; - gstate_c.needShaderTexClamp = false; - gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE; - - if (iter != cache.end()) { - entry = &iter->second; - // Validate the texture still matches the cache entry. - bool match = entry->Matches(dim, format, maxLevel); - const char *reason = "different params"; - - // Check for FBO - slow! - if (entry->framebuffer) { - if (match) { - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutAndTexRender, G3D, "Using rendered texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - - SetTextureFramebuffer(entry, entry->framebuffer); - return; - } else { - // Make sure we re-evaluate framebuffers. - DetachFramebuffer(entry, texaddr, entry->framebuffer); - reason = "detached framebuf"; - match = false; - } - } - - bool rehash = entry->GetHashStatus() == TexCacheEntry::STATUS_UNRELIABLE; - - // First let's see if another texture with the same address had a hashfail. - if (entry->status & TexCacheEntry::STATUS_CLUT_RECHECK) { - // Always rehash in this case, if one changed the rest all probably did. - rehash = true; - entry->status &= ~TexCacheEntry::STATUS_CLUT_RECHECK; - } else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) { - // Okay, just some parameter change - the data didn't change, no need to rehash. - rehash = false; - } - - if (match) { - if (entry->lastFrame != gpuStats.numFlips) { - u32 diff = gpuStats.numFlips - entry->lastFrame; - entry->numFrames++; - - if (entry->framesUntilNextFullHash < diff) { - // Exponential backoff up to 512 frames. Textures are often reused. - if (entry->numFrames > 32) { - // Also, try to add some "randomness" to avoid rehashing several textures the same frame. - entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + (entry->textureName & 15); - } else { - entry->framesUntilNextFullHash = entry->numFrames; - } - rehash = true; - } else { - entry->framesUntilNextFullHash -= diff; - } - } - - // If it's not huge or has been invalidated many times, recheck the whole texture. - if (entry->invalidHint > 180 || (entry->invalidHint > 15 && (dim >> 8) < 9 && (dim & 0xF) < 9)) { - entry->invalidHint = 0; - rehash = true; - } - - if (texhash != entry->hash) { - match = false; - } else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) { - rehash = false; - } - } - - if (match && (entry->status & TexCacheEntry::STATUS_TO_SCALE) && standardScaleFactor_ != 1 && texelsScaledThisFrame_ < TEXCACHE_MAX_TEXELS_SCALED) { - if ((entry->status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { - // INFO_LOG(G3D, "Reloading texture to do the scaling we skipped.."); - match = false; - reason = "scaling"; - } - } - - if (match) { - // TODO: Mark the entry reliable if it's been safe for long enough? - //got one! - if (entry->vkTex != lastBoundTexture) { - 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 { - nextChangeReason_ = reason; - nextNeedsChange_ = true; - } - } else { - VERBOSE_LOG(G3D, "No texture in cache, decoding..."); - TexCacheEntry entryNew = { 0 }; - cache[cachekey] = entryNew; - - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - - entry = &cache[cachekey]; - if (g_Config.bTextureBackoffCache) { - entry->status = TexCacheEntry::STATUS_HASHING; - } else { - entry->status = TexCacheEntry::STATUS_UNRELIABLE; - } - - 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->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->cluthash = cluthash; - - gstate_c.curTextureWidth = w; - gstate_c.curTextureHeight = h; - - // Before we go reading the texture from memory, let's check for render-to-texture. - // We must do this early so we have the right w/h. - entry->framebuffer = 0; - for (size_t i = 0, n = fbCache_.size(); i < n; ++i) { - auto framebuffer = fbCache_[i]; - AttachFramebuffer(entry, framebuffer->fb_address, framebuffer); - } - - // If we ended up with a framebuffer, attach it - no texture decoding needed. - if (entry->framebuffer) { - SetTextureFramebuffer(entry, entry->framebuffer); - } - - nextTexture_ = entry; - nextNeedsRehash_ = entry->framebuffer == nullptr; - // We still need to rebuild, to allocate a texture. But we'll bail early. - 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; - entry->status &= ~TexCacheEntry::STATUS_IS_SCALED; - } - // 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) { +void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, bool replaceImages) { entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; // For the estimate, we assume cluts always point to 8888 for simplicity. @@ -975,10 +527,10 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } - u64 cachekey = replacer.Enabled() ? entry->CacheKey() : 0; + 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); + 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; @@ -1074,7 +626,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff lastBoundTexture = entry->vkTex; ReplacedTextureDecodeInfo replacedInfo; - if (replacer.Enabled() && !replaced.Valid()) { + if (replacer_.Enabled() && !replaced.Valid()) { replacedInfo.cachekey = cachekey; replacedInfo.hash = entry->fullhash; replacedInfo.addr = entry->addr; @@ -1099,7 +651,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff int size = stride * mipHeight; uint32_t bufferOffset; VkBuffer texBuf; - void *data = uploadBuffer->Push(size, &bufferOffset, &texBuf); + void *data = drawEngine_->GetPushBufferForTextureData()->Push(size, &bufferOffset, &texBuf); if (replaced.Valid()) { replaced.Load(i, data, stride); } else { @@ -1109,8 +661,8 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, VulkanPushBuff break; } else LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt); - if (replacer.Enabled()) { - replacer.NotifyTextureDecoded(replacedInfo, data, stride, i, mipWidth, mipHeight); + if (replacer_.Enabled()) { + replacer_.NotifyTextureDecoded(replacedInfo, data, stride, i, mipWidth, mipHeight); } } entry->vkTex->texture_->UploadMip(i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp); @@ -1149,7 +701,7 @@ VkFormat TextureCacheVulkan::GetDestFormat(GETextureFormat format, GEPaletteForm } } -TextureCacheVulkan::TexCacheEntry::Status TextureCacheVulkan::CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int stride, int w, int h) { +TexCacheEntry::Status TextureCacheVulkan::CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int stride, int w, int h) { CheckAlphaResult res; switch (dstFmt) { case VULKAN_4444_FORMAT: @@ -1187,8 +739,8 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt u32 *pixelData = (u32 *)writePtr; int decPitch = rowPitch; if (scaleFactor > 1) { - tmpTexBufRearrange.resize(std::max(bufw, w) * h); - pixelData = tmpTexBufRearrange.data(); + tmpTexBufRearrange_.resize(std::max(bufw, w) * h); + pixelData = tmpTexBufRearrange_.data(); // We want to end up with a neatly packed texture for scaling. decPitch = w * bpp; } diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index f0318a3f26..d720b20be3 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -67,8 +67,6 @@ public: TextureCacheVulkan(Draw::DrawContext *draw, VulkanContext *vulkan); ~TextureCacheVulkan(); - void SetTexture(); - void Clear(bool delete_them); void StartFrame(); void EndFrame(); @@ -80,7 +78,7 @@ public: depalShaderCache_ = dpCache; } void SetShaderManager(ShaderManagerVulkan *sm) { - shaderManager_ = sm; + shaderManagerVulkan_ = sm; } void SetDrawEngine(DrawEngineVulkan *td) { drawEngine_ = td; @@ -90,27 +88,32 @@ public: lastBoundTexture = nullptr; gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); } + void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override { + if (!entry || entry->vkTex == lastBoundTexture) { + lastBoundTexture = nullptr; + } + } - void ApplyTexture(VulkanPushBuffer *uploadBuffer, VkImageView &imageView, VkSampler &sampler); + void GetVulkanHandles(VkImageView &imageView, VkSampler &sampler) { + imageView = imageView_; + sampler = sampler_; + } + void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key); protected: + void BindTexture(TexCacheEntry *entry) override; void Unbind() override; + void ReleaseTexture(TexCacheEntry *entry) override; private: - void Decimate(); // Run this once per frame to get rid of old textures. - void DeleteTexture(TexCache::iterator it); void UpdateSamplingParams(TexCacheEntry &entry, SamplerCacheKey &key); void LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt); VkFormat GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; TexCacheEntry::Status CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int stride, int w, int h); - u32 GetCurrentClutHash(); - void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple); - void ApplyTextureFramebuffer(VkCommandBuffer cmd, TexCacheEntry *entry, VirtualFramebuffer *framebuffer, VkImageView &image, VkSampler &sampler); - void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key); + void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; - 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); + void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override; + void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override; VulkanContext *vulkan_; VulkanDeviceAllocator *allocator_; @@ -119,8 +122,6 @@ private: TextureScalerVulkan scaler; - u32 clutHash_; - CachedTextureVulkan *lastBoundTexture; int decimationCounter_; @@ -129,8 +130,12 @@ private: FramebufferManagerVulkan *framebufferManagerVulkan_; DepalShaderCacheVulkan *depalShaderCache_; - ShaderManagerVulkan *shaderManager_; + ShaderManagerVulkan *shaderManagerVulkan_; DrawEngineVulkan *drawEngine_; + + // Bound state to emulate an API similar to the others + VkImageView imageView_ = VK_NULL_HANDLE; + VkSampler sampler_ = VK_NULL_HANDLE; }; VkFormat getClutDestFormatVulkan(GEPaletteFormat format);