diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 48c9905e38..c0b965e9b1 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -49,7 +49,8 @@ static const int VERSION = 1; static const int MAX_MIP_LEVELS = 12; // 12 should be plenty, 8 is the max mip levels supported by the PSP. TextureReplacer::TextureReplacer() { - none_.alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; + none_.initDone_ = true; + none_.prepareDone_ = true; } TextureReplacer::~TextureReplacer() { @@ -373,7 +374,7 @@ u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, GETextureForm } } -ReplacedTexture &TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, int h) { +ReplacedTexture &TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, int h, double budget) { // Only actually replace if we're replacing. We might just be saving. if (!Enabled() || !g_Config.bReplaceTextures) { return none_; @@ -382,13 +383,18 @@ ReplacedTexture &TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, ReplacementCacheKey replacementKey(cachekey, hash); auto it = cache_.find(replacementKey); if (it != cache_.end()) { + if (!it->second.prepareDone_ && budget > 0.0) { + // We don't do this on a thread, but we only do it while within budget. + PopulateReplacement(&it->second, cachekey, hash, w, h); + } return it->second; } // Okay, let's construct the result. ReplacedTexture &result = cache_[replacementKey]; - result.alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; - PopulateReplacement(&result, cachekey, hash, w, h); + if (!g_Config.bReplaceTexturesAllowLate || budget > 0.0) { + PopulateReplacement(&result, cachekey, hash, w, h); + } return result; } @@ -433,7 +439,7 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, break; } - result->alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; + result->prepareDone_ = true; } enum class ReplacedImageType { @@ -815,11 +821,13 @@ bool ReplacedTexture::IsReady(double budget) { } // Loaded already, or not yet on a thread? - if (!levelData_.empty()) + if (initDone_ && !levelData_.empty()) return true; // Let's not even start a new texture if we're already behind. if (budget < 0.0) return false; + if (!prepareDone_) + return false; if (g_Config.bReplaceTexturesAllowLate) { if (threadWaitable_) @@ -829,10 +837,11 @@ bool ReplacedTexture::IsReady(double budget) { if (threadWaitable_->WaitFor(budget)) { // If we finished all the levels, we're done. - return !levelData_.empty(); + return initDone_ && !levelData_.empty(); } } else { Prepare(); + _assert_(initDone_); return true; } @@ -842,16 +851,19 @@ bool ReplacedTexture::IsReady(double budget) { void ReplacedTexture::Prepare() { std::unique_lock lock(mutex_); - if (cancelPrepare_) + if (cancelPrepare_) { + initDone_ = true; return; + } - levelData_.resize(NumLevels()); - for (int i = 0; i < NumLevels(); ++i) { + levelData_.resize(levels_.size()); + for (int i = 0; i < (int)levels_.size(); ++i) { if (cancelPrepare_) break; PrepareData(i); } + initDone_ = true; if (!cancelPrepare_ && threadWaitable_) threadWaitable_->Notify(); } @@ -975,6 +987,8 @@ bool ReplacedTexture::Load(int level, void *out, int rowPitch) { _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); _assert_msg_(out != nullptr && rowPitch > 0, "Invalid out/pitch"); + if (!initDone_) + return false; if (levelData_.empty()) return false; diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index e01a03d2a4..b32d31a7d3 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -118,10 +118,20 @@ struct ReplacedTexture { ~ReplacedTexture(); inline bool Valid() const { + if (!initDone_) + return false; return !levels_.empty(); } + inline bool IsInvalid() const { + if (!initDone_) + return false; + return levels_.empty(); + } + bool GetSize(int level, int &w, int &h) const { + if (!initDone_) + return false; if ((size_t)level < levels_.size()) { w = levels_[level].w; h = levels_[level].h; @@ -131,12 +141,16 @@ struct ReplacedTexture { } int NumLevels() const { + if (!initDone_) + return 0; return (int)levels_.size(); } Draw::DataFormat Format(int level) const { - if ((size_t)level < levels_.size()) { - return levels_[level].fmt; + if (initDone_) { + if ((size_t)level < levels_.size()) { + return levels_[level].fmt; + } } return Draw::DataFormat::R8G8B8A8_UNORM; } @@ -156,11 +170,13 @@ protected: std::vector levels_; std::vector> levelData_; - ReplacedTextureAlpha alphaStatus_; + ReplacedTextureAlpha alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; double lastUsed_ = 0.0; LimitedWaitable *threadWaitable_ = nullptr; std::mutex mutex_; bool cancelPrepare_ = false; + bool initDone_ = false; + bool prepareDone_ = false; friend TextureReplacer; friend ReplacedTextureTask; @@ -196,7 +212,7 @@ public: u32 ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV); - ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h); + ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h, double budget); bool FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering); ReplacedTexture &FindNone() { return none_; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 72839eb97a..a5b2749d62 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -482,7 +482,14 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { int h0 = gstate.getTextureHeight(0); int d0 = 1; ReplacedTexture &replaced = FindReplacement(entry, w0, h0, d0); - if (replaced.Valid()) { + if (replaced.IsInvalid()) { + entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; + if (g_Config.bSaveNewTextures) { + // Load once more to actually save. + match = false; + reason = "replacing"; + } + } else { match = false; reason = "replacing"; } @@ -1303,22 +1310,22 @@ ReplacedTexture &TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & constexpr double MAX_BUDGET_PER_TEX = 0.25 / 60.0; double replaceStart = time_now_d(); + double budget = std::min(MAX_BUDGET_PER_TEX, replacementFrameBudget_ - replacementTimeThisFrame_); u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.IsReady(std::min(MAX_BUDGET_PER_TEX, replacementFrameBudget_ - replacementTimeThisFrame_))) { + ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h, budget); + if (replaced.IsReady(budget)) { if (replaced.GetSize(0, w, h)) { - replacementTimeThisFrame_ += time_now_d() - replaceStart; - - // Consider it already "scaled" and remove any delayed replace flag. + // Consider it already "scaled." entry->status |= TexCacheEntry::STATUS_IS_SCALED; - entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - return replaced; } - } else if (replaced.Valid()) { + + // Remove the flag, even if it was invalid. + entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; + } else if (!replaced.IsInvalid()) { entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } replacementTimeThisFrame_ += time_now_d() - replaceStart; - return replacer_.FindNone(); + return replaced; } // This is only used in the GLES backend, where we don't point these to video memory. @@ -2375,7 +2382,7 @@ void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, i } } - if (replacer_.Enabled()) { + if (replacer_.Enabled() && replaced.IsInvalid()) { ReplacedTextureDecodeInfo replacedInfo; replacedInfo.cachekey = entry.CacheKey(); replacedInfo.hash = entry.fullhash; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index ba860c1be5..603b064f8b 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -464,7 +464,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { // Any texture scaling is gonna move away from the original 16-bit format, if any. VkFormat actualFmt = plan.scaleFactor > 1 ? VULKAN_8888_FORMAT : dstFmt; if (plan.replaced->Valid()) { - actualFmt = ToVulkanFormat(plan.replaced->Format(0)); + actualFmt = ToVulkanFormat(plan.replaced->Format(plan.baseLevelSrc)); } bool computeUpload = false; @@ -536,7 +536,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { ReplacedTextureDecodeInfo replacedInfo; bool willSaveTex = false; - if (replacer_.Enabled() && !plan.replaced->Valid() && plan.depth == 1) { + if (replacer_.Enabled() && plan.replaced->IsInvalid() && plan.depth == 1) { replacedInfo.cachekey = entry->CacheKey(); replacedInfo.hash = entry->fullhash; replacedInfo.addr = entry->addr; @@ -565,7 +565,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { int mipWidth = mipUnscaledWidth * plan.scaleFactor; int mipHeight = mipUnscaledHeight * plan.scaleFactor; if (plan.replaced->Valid()) { - plan.replaced->GetSize(i, mipWidth, mipHeight); + plan.replaced->GetSize(plan.baseLevelSrc + i, mipWidth, mipHeight); } int bpp = actualFmt == VULKAN_8888_FORMAT ? 4 : 2; // output bpp @@ -595,7 +595,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { // Directly load the replaced image. data = drawEngine_->GetPushBufferForTextureData()->PushAligned(size, &bufferOffset, &texBuf, pushAlignment); double replaceStart = time_now_d(); - plan.replaced->Load(i, data, stride); // if it fails, it'll just be garbage data... OK for now. + plan.replaced->Load(plan.baseLevelSrc + i, data, stride); // if it fails, it'll just be garbage data... OK for now. replacementTimeThisFrame_ += time_now_d() - replaceStart; VK_PROFILE_BEGIN(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT, "Copy Upload (replaced): %dx%d", mipWidth, mipHeight); @@ -636,7 +636,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { int w = dataScaled ? mipWidth : mipUnscaledWidth; int h = dataScaled ? mipHeight : mipUnscaledHeight; // At this point, data should be saveData, and not slow. - replacer_.NotifyTextureDecoded(replacedInfo, data, stride, i, w, h); + replacer_.NotifyTextureDecoded(replacedInfo, data, stride, plan.baseLevelSrc + i, w, h); } } }