diff --git a/Core/Config.cpp b/Core/Config.cpp index 2afcf76cf584..8143123e8867 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -887,6 +887,7 @@ static ConfigSetting graphicsSettings[] = { ReportedConfigSetting("ReplaceTextures", &g_Config.bReplaceTextures, true, true, true), ReportedConfigSetting("SaveNewTextures", &g_Config.bSaveNewTextures, false, true, true), ConfigSetting("IgnoreTextureFilenames", &g_Config.bIgnoreTextureFilenames, false, true, true), + ConfigSetting("ReplaceTexturesAllowLate", &g_Config.bReplaceTexturesAllowLate, true, true, true), ReportedConfigSetting("TexScalingLevel", &g_Config.iTexScalingLevel, 1, true, true), ReportedConfigSetting("TexScalingType", &g_Config.iTexScalingType, 0, true, true), diff --git a/Core/Config.h b/Core/Config.h index b6bf7f062e06..7ded5c9aa012 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -197,6 +197,7 @@ struct Config { bool bReplaceTextures; bool bSaveNewTextures; bool bIgnoreTextureFilenames; + bool bReplaceTexturesAllowLate; int iTexScalingLevel; // 0 = auto, 1 = off, 2 = 2x, ..., 5 = 5x int iTexScalingType; // 0 = xBRZ, 1 = Hybrid bool bTexDeposterize; diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index daa687c0b790..3d40e302d0f0 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -17,6 +17,7 @@ #include "ppsspp_config.h" #include +#include #include #include #include @@ -31,6 +32,7 @@ #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" #include "Common/Thread/ParallelLoop.h" +#include "Common/TimeUtil.h" #include "Core/Config.h" #include "Core/Host.h" #include "Core/System.h" @@ -621,6 +623,15 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl savedCache_[replacementKey] = saved; } +void TextureReplacer::Decimate(bool forcePressure) { + // Allow replacements to be cached for a long time, although they're large. + const double age = forcePressure ? 90.0 : 1800.0; + const double threshold = time_now_d() - age; + for (auto &item : cache_) { + item.second.PurgeIfOlder(threshold); + } +} + template static typename std::unordered_map::const_iterator LookupWildcard(const std::unordered_map &map, Key &key, u64 cachekey, u32 hash, bool ignoreAddress) { auto alias = map.find(key); @@ -733,15 +744,114 @@ float TextureReplacer::LookupReduceHashRange(int& w, int& h) { } } -bool ReplacedTexture::Load(int level, void *out, int rowPitch) { +class LimitedWaitable : public Waitable { +public: + LimitedWaitable() { + triggered_ = false; + } + + void Wait() override { + if (!triggered_) { + std::unique_lock lock(mutex_); + cond_.wait(lock, [&] { return !triggered_; }); + } + } + + bool WaitFor(double budget) { + uint32_t us = budget > 0 ? (uint32_t)(budget * 1000000.0) : 0; + if (!triggered_) { + if (us == 0) + return false; + std::unique_lock lock(mutex_); + cond_.wait_for(lock, std::chrono::microseconds(us), [&] { return !triggered_; }); + } + return triggered_; + } + + void Notify() { + std::unique_lock lock(mutex_); + triggered_ = true; + cond_.notify_all(); + } + +private: + std::condition_variable cond_; + std::mutex mutex_; + std::atomic triggered_; +}; + +class ReplacedTextureTask : public Task { +public: + ReplacedTextureTask(ReplacedTexture &tex, LimitedWaitable *w) : tex_(tex), waitable_(w) { + } + + void Run() override { + tex_.Prepare(); + waitable_->Notify(); + } + +private: + ReplacedTexture &tex_; + LimitedWaitable *waitable_; +}; + +bool ReplacedTexture::IsReady(double budget) { + lastUsed_ = time_now_d(); + if (threadWaitable_) { + if (!threadWaitable_->WaitFor(budget)) { + return false; + } else { + threadWaitable_->WaitAndRelease(); + threadWaitable_ = nullptr; + } + } + + // Loaded already, or not yet on a thread? + if (!levelData_.empty()) + return true; + // Let's not even start a new texture if we're already behind. + if (budget < 0.0) + return false; + + if (g_Config.bReplaceTexturesAllowLate) { + threadWaitable_ = new LimitedWaitable(); + g_threadManager.EnqueueTask(new ReplacedTextureTask(*this, threadWaitable_), TaskType::IO_BLOCKING); + + if (threadWaitable_->WaitFor(budget)) { + threadWaitable_->WaitAndRelease(); + threadWaitable_ = nullptr; + + // If we finished all the levels, we're done. + return !levelData_.empty(); + } + } else { + Prepare(); + return true; + } + + // Still pending on thread. + return false; +} + +void ReplacedTexture::Prepare() { + levelData_.resize(MaxLevel() + 1); + for (int i = 0; i <= MaxLevel(); ++i) { + if (cancelPrepare_) + break; + PrepareData(i); + } +} + +void ReplacedTexture::PrepareData(int level) { _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); - _assert_msg_(out != nullptr && rowPitch > 0, "Invalid out/pitch"); const ReplacedTextureLevel &info = levels_[level]; + std::vector &out = levelData_[level]; FILE *fp = File::OpenCFile(info.file, "rb"); if (!fp) { - return false; + // Leaving the data sized at zero means failure. + return; } auto imageType = Identify(fp); @@ -751,29 +861,36 @@ bool ReplacedTexture::Load(int level, void *out, int rowPitch) { if (!zim) { ERROR_LOG(G3D, "Failed to allocate memory for texture replacement"); fclose(fp); - return false; + return; } if (fread(&zim[0], 1, zimSize, fp) != zimSize) { ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", info.file.c_str()); fclose(fp); - return false; + return; } int w, h, f; uint8_t *image; - const int MIN_LINES_PER_THREAD = 4; if (LoadZIMPtr(&zim[0], zimSize, &w, &h, &f, &image)) { - ParallelRangeLoop(&g_threadManager, [&](int l, int h) { - for (int y = l; y < h; ++y) { - memcpy((uint8_t *)out + rowPitch * y, image + w * 4 * y, w * 4); + if (w > info.w || h > info.h) { + ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); + fclose(fp); + return; + } + + out.resize(info.w * info.h * 4); + if (w == info.w) { + memcpy(&out[0], image, info.w * 4 * info.h); + } else { + for (int y = 0; y < h; ++y) { + memcpy(&out[info.w * 4 * y], image + w * 4 * y, w * 4); } - }, 0, h, MIN_LINES_PER_THREAD); + } free(image); } - // This will only check the hashed bits. - CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)out, rowPitch / sizeof(u32), w, h); + CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)&out[0], info.w, w, h); if (res == CHECKALPHA_ANY || level == 0) { alphaStatus_ = ReplacedTextureAlpha(res); } @@ -784,7 +901,12 @@ bool ReplacedTexture::Load(int level, void *out, int rowPitch) { if (!png_image_begin_read_from_stdio(&png, fp)) { ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s", info.file.c_str(), png.message); fclose(fp); - return false; + return; + } + if (png.width > (uint32_t)info.w || png.height > (uint32_t)info.h) { + ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); + fclose(fp); + return; } bool checkedAlpha = false; @@ -797,16 +919,18 @@ bool ReplacedTexture::Load(int level, void *out, int rowPitch) { } png.format = PNG_FORMAT_RGBA; - if (!png_image_finish_read(&png, nullptr, out, rowPitch, nullptr)) { + out.resize(info.w * info.h * 4); + if (!png_image_finish_read(&png, nullptr, &out[0], info.w * 4, nullptr)) { ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", info.file.c_str(), png.message); fclose(fp); - return false; + out.resize(0); + return; } png_image_free(&png); if (!checkedAlpha) { // This will only check the hashed bits. - CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)out, rowPitch / sizeof(u32), png.width, png.height); + CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)&out[0], info.w, png.width, png.height); if (res == CHECKALPHA_ANY || level == 0) { alphaStatus_ = ReplacedTextureAlpha(res); } @@ -814,6 +938,47 @@ bool ReplacedTexture::Load(int level, void *out, int rowPitch) { } fclose(fp); +} + +void ReplacedTexture::PurgeIfOlder(double t) { + if (lastUsed_ < t && !threadWaitable_) { + levelData_.clear(); + } +} + +ReplacedTexture::~ReplacedTexture() { + if (threadWaitable_) { + cancelPrepare_ = true; + threadWaitable_->WaitAndRelease(); + threadWaitable_ = nullptr; + } +} + +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 (levelData_.empty()) + return false; + + const ReplacedTextureLevel &info = levels_[level]; + const std::vector &data = levelData_[level]; + + if (data.empty()) + return false; + _assert_msg_(data.size() == info.w * info.h * 4, "Data has wrong size"); + + if (rowPitch == info.w * 4) { + ParallelMemcpy(&g_threadManager, out, &data[0], info.w * 4 * info.h); + } else { + const int MIN_LINES_PER_THREAD = 4; + ParallelRangeLoop(&g_threadManager, [&](int l, int h) { + for (int y = l; y < h; ++y) { + memcpy((uint8_t *)out + rowPitch * y, &data[0] + info.w * 4 * y, info.w * 4); + } + }, 0, info.h, MIN_LINES_PER_THREAD); + } + return true; } diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 92fac7b746ae..6deefe8daf09 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -32,6 +32,8 @@ class IniFile; class TextureCacheCommon; class TextureReplacer; +class ReplacedTextureTask; +class LimitedWaitable; enum class ReplacedTextureFormat { F_5650, @@ -125,6 +127,8 @@ namespace std { } struct ReplacedTexture { + ~ReplacedTexture(); + inline bool Valid() { return !levels_.empty(); } @@ -153,13 +157,24 @@ struct ReplacedTexture { return (u8)alphaStatus_; } + bool IsReady(double budget); + bool Load(int level, void *out, int rowPitch); protected: + void Prepare(); + void PrepareData(int level); + void PurgeIfOlder(double t); + std::vector levels_; + std::vector> levelData_; ReplacedTextureAlpha alphaStatus_; + double lastUsed_ = 0.0; + LimitedWaitable *threadWaitable_ = nullptr; + bool cancelPrepare_ = false; friend TextureReplacer; + friend ReplacedTextureTask; }; struct ReplacedTextureDecodeInfo { @@ -188,9 +203,14 @@ class TextureReplacer { ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h); bool FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering); + ReplacedTexture &FindNone() { + return none_; + } void NotifyTextureDecoded(const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int w, int h); + void Decimate(bool forcePressure); + static bool GenerateIni(const std::string &gameID, Path &generatedFilename); protected: diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 4b872306b06e..b694730c4755 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -22,6 +22,7 @@ #include "Common/Profiler/Profiler.h" #include "Common/MemoryUtil.h" #include "Common/StringUtils.h" +#include "Common/TimeUtil.h" #include "Core/Config.h" #include "Core/Debugger/MemBlockInfo.h" #include "Core/Reporting.h" @@ -472,6 +473,15 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { reason = "scaling"; } } + if (match && (entry->status & TexCacheEntry::STATUS_TO_REPLACE) && replacementTimeThisFrame_ < replacementFrameBudget_) { + int w0 = gstate.getTextureWidth(0); + int h0 = gstate.getTextureHeight(0); + ReplacedTexture &replaced = FindReplacement(entry, w0, h0); + if (replaced.Valid()) { + match = false; + reason = "replacing"; + } + } if (match) { // got one! @@ -720,6 +730,7 @@ void TextureCacheCommon::Decimate(bool forcePressure) { } DecimateVideos(); + replacer_.Decimate(forcePressure); } void TextureCacheCommon::DecimateVideos() { @@ -1263,6 +1274,29 @@ u32 TextureCacheCommon::EstimateTexMemoryUsage(const TexCacheEntry *entry) { return pixelSize << (dimW + dimH); } +ReplacedTexture &TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int &w, int &h) { + // Allow some delay to reduce pop-in. + constexpr double MAX_BUDGET_PER_TEX = 0.25 / 60.0; + + double replaceStart = time_now_d(); + 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_))) { + if (replaced.GetSize(0, w, h)) { + replacementTimeThisFrame_ += time_now_d() - replaceStart; + + // Consider it already "scaled" and remove any delayed replace flag. + entry->status |= TexCacheEntry::STATUS_IS_SCALED; + entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; + return replaced; + } + } else if (replaced.Valid()) { + entry->status |= TexCacheEntry::STATUS_TO_REPLACE; + } + replacementTimeThisFrame_ += time_now_d() - replaceStart; + return replacer_.FindNone(); +} + static void ReverseColors(void *dstBuf, const void *srcBuf, GETextureFormat fmt, int numPixels, bool useBGRA) { switch (fmt) { case GE_TFMT_4444: diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index e6a3c0b58f80..26d6d6d0e66b 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -123,16 +123,17 @@ struct TexCacheEntry { STATUS_CLUT_RECHECK = 0x20, // Another texture with same addr had a hashfail. STATUS_TO_SCALE = 0x80, // Pending texture scaling in a later frame. STATUS_IS_SCALED = 0x100, // Has been scaled (can't be replaceImages'd.) + STATUS_TO_REPLACE = 0x0200, // Pending texture replacement. // When hashing large textures, we optimize 512x512 down to 512x272 by default, since this // is commonly the only part accessed. If access is made above 272, we hash the entire // texture, and set this flag to allow scaling the texture just once for the new hash. - STATUS_FREE_CHANGE = 0x200, // Allow one change before marking "frequent". + STATUS_FREE_CHANGE = 0x0400, // Allow one change before marking "frequent". - STATUS_BAD_MIPS = 0x400, // Has bad or unusable mipmap levels. + STATUS_BAD_MIPS = 0x0800, // Has bad or unusable mipmap levels. - STATUS_FRAMEBUFFER_OVERLAP = 0x800, + STATUS_FRAMEBUFFER_OVERLAP = 0x1000, - STATUS_FORCE_REBUILD = 0x1000, + STATUS_FORCE_REBUILD = 0x2000, }; // Status, but int so we can zero initialize. @@ -277,6 +278,7 @@ class TextureCacheCommon { void DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, bool reverseColors, bool useBGRA, bool expandTo32Bit); void UnswizzleFromMem(u32 *dest, u32 destPitch, const u8 *texptr, u32 bufw, u32 height, u32 bytesPerPixel); void ReadIndexedTex(u8 *out, int outPitch, int level, const u8 *texptr, int bytesPerIndex, int bufw, bool expandTo32Bit); + ReplacedTexture &FindReplacement(TexCacheEntry *entry, int &w, int &h); template inline const T *GetCurrentClut() { @@ -334,6 +336,9 @@ class TextureCacheCommon { int decimationCounter_; int texelsScaledThisFrame_ = 0; int timesInvalidatedAllThisFrame_ = 0; + double replacementTimeThisFrame_ = 0; + // TODO: Maybe vary by FPS... + double replacementFrameBudget_ = 0.5 / 60.0; TexCache cache_; u32 cacheSizeEstimate_ = 0; diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index be61a5ae48dc..199f408e4ae5 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -21,6 +21,7 @@ #include +#include "Common/TimeUtil.h" #include "Core/MemMap.h" #include "Core/Reporting.h" #include "GPU/ge_constants.h" @@ -169,6 +170,7 @@ void TextureCacheD3D11::InvalidateLastTexture() { void TextureCacheD3D11::StartFrame() { InvalidateLastTexture(); timesInvalidatedAllThisFrame_ = 0; + replacementTimeThisFrame_ = 0.0; if (texelsScaledThisFrame_) { // INFO_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); @@ -483,14 +485,12 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } - u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.GetSize(0, w, h)) { + ReplacedTexture &replaced = FindReplacement(entry, w, h); + if (replaced.Valid()) { // We're replacing, so we won't scale. scaleFactor = 1; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; maxLevel = replaced.MaxLevel(); badMipSizes = false; } @@ -678,7 +678,9 @@ void TextureCacheD3D11::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture & if (replaced.GetSize(level, w, h)) { mapData = (u32 *)AllocateAlignedMemory(w * h * sizeof(u32), 16); mapRowPitch = w * 4; + double replaceStart = time_now_d(); replaced.Load(level, mapData, mapRowPitch); + replacementTimeThisFrame_ += time_now_d() - replaceStart; dstFmt = ToDXGIFormat(replaced.Format(level)); } else { GETextureFormat tfmt = (GETextureFormat)entry.format; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 0b1d536ed7f9..26f15e6998b3 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -18,6 +18,7 @@ #include #include +#include "Common/TimeUtil.h" #include "Core/MemMap.h" #include "Core/Reporting.h" #include "GPU/ge_constants.h" @@ -134,6 +135,7 @@ void TextureCacheDX9::ApplySamplingParams(const SamplerCacheKey &key) { void TextureCacheDX9::StartFrame() { InvalidateLastTexture(); timesInvalidatedAllThisFrame_ = 0; + replacementTimeThisFrame_ = 0.0; if (texelsScaledThisFrame_) { VERBOSE_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); @@ -438,14 +440,12 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } - u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.GetSize(0, w, h)) { + ReplacedTexture &replaced = FindReplacement(entry, w, h); + if (replaced.Valid()) { // We're replacing, so we won't scale. scaleFactor = 1; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; maxLevel = replaced.MaxLevel(); badMipSizes = false; } @@ -615,7 +615,9 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re gpuStats.numTexturesDecoded++; if (replaced.GetSize(level, w, h)) { + double replaceStart = time_now_d(); replaced.Load(level, rect.pBits, rect.Pitch); + replacementTimeThisFrame_ += time_now_d() - replaceStart; dstFmt = ToD3D9Format(replaced.Format(level)); } else { GETextureFormat tfmt = (GETextureFormat)entry.format; diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 1dd35b39c0fc..b95f6881face 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -24,6 +24,7 @@ #include "Common/Math/math_util.h" #include "Common/Profiler/Profiler.h" #include "Common/GPU/OpenGL/GLRenderManager.h" +#include "Common/TimeUtil.h" #include "Core/Config.h" #include "Core/Host.h" @@ -152,6 +153,7 @@ static void ConvertColors(void *dstBuf, const void *srcBuf, Draw::DataFormat dst void TextureCacheGLES::StartFrame() { InvalidateLastTexture(); timesInvalidatedAllThisFrame_ = 0; + replacementTimeThisFrame_ = 0.0; GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); if (!lowMemoryMode_ && renderManager->SawOutOfMemory()) { @@ -502,14 +504,12 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } - u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.GetSize(0, w, h)) { + ReplacedTexture &replaced = FindReplacement(entry, w, h); + if (replaced.Valid()) { // We're replacing, so we won't scale. scaleFactor = 1; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; maxLevel = replaced.MaxLevel(); badMipSizes = false; } @@ -658,7 +658,9 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r int bpp = replaced.Format(level) == ReplacedTextureFormat::F_8888 ? 4 : 2; decPitch = w * bpp; uint8_t *rearrange = (uint8_t *)AllocateAlignedMemory(decPitch * h, 16); + double replaceStart = time_now_d(); replaced.Load(level, rearrange, decPitch); + replacementTimeThisFrame_ += time_now_d() - replaceStart; pixelData = rearrange; dstFmt = ToDataFormat(replaced.Format(level)); diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index ee26533f43c6..47b2fe892448 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -28,6 +28,7 @@ #include "Common/Data/Convert/ColorConv.h" #include "Common/StringUtils.h" +#include "Common/TimeUtil.h" #include "Core/Config.h" #include "Core/Host.h" #include "Core/MemMap.h" @@ -442,6 +443,7 @@ void TextureCacheVulkan::StartFrame() { timesInvalidatedAllThisFrame_ = 0; texelsScaledThisFrame_ = 0; + replacementTimeThisFrame_ = 0.0; if (clearCacheNextFrame_) { Clear(true); @@ -773,14 +775,12 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } - u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.GetSize(0, w, h)) { + ReplacedTexture &replaced = FindReplacement(entry, w, h); + if (replaced.Valid()) { // We're replacing, so we won't scale. scaleFactor = 1; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; maxLevel = replaced.MaxLevel(); badMipSizes = false; } @@ -899,7 +899,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { ReplacedTextureDecodeInfo replacedInfo; if (replacer_.Enabled() && !replaced.Valid()) { - replacedInfo.cachekey = cachekey; + replacedInfo.cachekey = entry->CacheKey(); replacedInfo.hash = entry->fullhash; replacedInfo.addr = entry->addr; replacedInfo.isVideo = IsVideo(entry->addr); @@ -934,7 +934,9 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { if (replaced.Valid()) { // Directly load the replaced image. data = drawEngine_->GetPushBufferForTextureData()->PushAligned(size, &bufferOffset, &texBuf, pushAlignment); + double replaceStart = time_now_d(); replaced.Load(i, data, stride); // if it fails, it'll just be garbage data... OK for now. + replacementTimeThisFrame_ += time_now_d() - replaceStart; entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp); } else { auto dispatchCompute = [&](VkDescriptorSet descSet) {