From 2356280a9ccd87e2624e06ced8d9ad31287681dc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 17 Oct 2021 08:54:45 -0700 Subject: [PATCH 01/12] Replacement: Add structure for delayed loading. --- Core/TextureReplacer.cpp | 4 ++++ Core/TextureReplacer.h | 2 ++ GPU/Common/TextureCacheCommon.cpp | 4 ++++ GPU/Common/TextureCacheCommon.h | 12 ++++++++---- GPU/D3D11/TextureCacheD3D11.cpp | 24 ++++++++++++++++++------ GPU/Directx9/TextureCacheDX9.cpp | 24 ++++++++++++++++++------ GPU/GLES/TextureCacheGLES.cpp | 24 ++++++++++++++++++------ GPU/Vulkan/TextureCacheVulkan.cpp | 24 ++++++++++++++++++------ 8 files changed, 90 insertions(+), 28 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index daa687c0b7..38c44a0622 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -733,6 +733,10 @@ float TextureReplacer::LookupReduceHashRange(int& w, int& h) { } } +bool ReplacedTexture::IsReady(double budget) { + return true; +} + 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"); diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 92fac7b746..110f9df57b 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -153,6 +153,8 @@ struct ReplacedTexture { return (u8)alphaStatus_; } + bool IsReady(double budget); + bool Load(int level, void *out, int rowPitch); protected: diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 4b872306b0..8661018007 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -472,6 +472,10 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { reason = "scaling"; } } + if (match && (entry->status & TexCacheEntry::STATUS_TO_REPLACE) && replacementTimeThisFrame_ <= replacementFrameBudget_) { + match = false; + reason = "replacing"; + } if (match) { // got one! diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index e6a3c0b58f..ceb2aa96ff 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. @@ -334,6 +335,9 @@ protected: int decimationCounter_; int texelsScaledThisFrame_ = 0; int timesInvalidatedAllThisFrame_ = 0; + double replacementTimeThisFrame_ = 0; + // TODO: Maybe vary by FPS... + double replacementFrameBudget_ = 0.75 / 60.0; TexCache cache_; u32 cacheSizeEstimate_ = 0; diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index be61a5ae48..1a8b34a54c 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_); @@ -486,13 +488,21 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); + double replaceStart = time_now_d(); 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; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; - maxLevel = replaced.MaxLevel(); - badMipSizes = false; + if (replaced.IsReady(replacementFrameBudget_ - replacementTimeThisFrame_)) { + if (replaced.GetSize(0, w, h)) { + replacementTimeThisFrame_ += time_now_d() - replaceStart; + + // We're replacing, so we won't scale. + scaleFactor = 1; + entry->status |= TexCacheEntry::STATUS_IS_SCALED; + entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; + maxLevel = replaced.MaxLevel(); + badMipSizes = false; + } + } else if (replaced.MaxLevel() >= 0) { + entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } // Don't scale the PPGe texture. @@ -678,7 +688,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 0b1d536ed7..20f5c696cc 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_); @@ -441,13 +443,21 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry) { u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); + double replaceStart = time_now_d(); 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; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; - maxLevel = replaced.MaxLevel(); - badMipSizes = false; + if (replaced.IsReady(replacementFrameBudget_ - replacementTimeThisFrame_)) { + if (replaced.GetSize(0, w, h)) { + replacementTimeThisFrame_ += time_now_d() - replaceStart; + + // We're replacing, so we won't scale. + scaleFactor = 1; + entry->status |= TexCacheEntry::STATUS_IS_SCALED; + entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; + maxLevel = replaced.MaxLevel(); + badMipSizes = false; + } + } else if (replaced.MaxLevel() >= 0) { + entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } // Don't scale the PPGe texture. @@ -615,7 +625,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 1dd35b39c0..9a972792c4 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()) { @@ -505,13 +507,21 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); + double replaceStart = time_now_d(); 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; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; - maxLevel = replaced.MaxLevel(); - badMipSizes = false; + if (replaced.IsReady(replacementFrameBudget_ - replacementTimeThisFrame_)) { + if (replaced.GetSize(0, w, h)) { + replacementTimeThisFrame_ += time_now_d() - replaceStart; + + // We're replacing, so we won't scale. + scaleFactor = 1; + entry->status |= TexCacheEntry::STATUS_IS_SCALED; + entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; + maxLevel = replaced.MaxLevel(); + badMipSizes = false; + } + } else if (replaced.MaxLevel() >= 0) { + entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } // Don't scale the PPGe texture. @@ -658,7 +668,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 ee26533f43..7df7877109 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); @@ -776,13 +778,21 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); + double replaceStart = time_now_d(); 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; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; - maxLevel = replaced.MaxLevel(); - badMipSizes = false; + if (replaced.IsReady(replacementFrameBudget_ - replacementTimeThisFrame_)) { + if (replaced.GetSize(0, w, h)) { + replacementTimeThisFrame_ += time_now_d() - replaceStart; + + // We're replacing, so we won't scale. + scaleFactor = 1; + entry->status |= TexCacheEntry::STATUS_IS_SCALED; + entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; + maxLevel = replaced.MaxLevel(); + badMipSizes = false; + } + } else if (replaced.MaxLevel() >= 0) { + entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } bool hardwareScaling = g_Config.bTexHardwareScaling && (uploadCS_ != VK_NULL_HANDLE || copyCS_ != VK_NULL_HANDLE); @@ -934,7 +944,9 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { if (replaced.Valid()) { // Directly load the replaced image. data = drawEngine_->GetPushBufferForTextureData()->PushAligned(size, &bufferOffset, &texBuf, pushAlignment); + 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) { From 36fc2c2628f33da40c8858bf06b94fffce907fdf Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 17 Oct 2021 09:16:54 -0700 Subject: [PATCH 02/12] Replacement: Purge old cached decoded textures. Not actually decoding into the cache, just setup. --- Core/TextureReplacer.cpp | 38 ++++++++++++++++++++++++++++++- Core/TextureReplacer.h | 7 ++++++ GPU/Common/TextureCacheCommon.cpp | 1 + 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 38c44a0622..d92fb9e037 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -31,6 +31,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 +622,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); @@ -734,7 +744,33 @@ float TextureReplacer::LookupReduceHashRange(int& w, int& h) { } bool ReplacedTexture::IsReady(double budget) { - return true; + lastUsed_ = time_now_d(); + + if (levelData_.size() == levels_.size()) + return true; + if (budget <= 0.0) + return false; + + double deadline = lastUsed_ + budget; + for (int i = (int)levelData_.size(); i <= MaxLevel(); ++i) { + levelData_.resize(i + 1); + PrepareData(i); + if (time_now_d() >= deadline) { + break; + } + } + // If we finished all the levels, we're done. + return levelData_.size() == levels_.size(); +} + +void ReplacedTexture::PrepareData(int level) { + // TODO +} + +void ReplacedTexture::PurgeIfOlder(double t) { + if (lastUsed_ < t) { + levelData_.clear(); + } } bool ReplacedTexture::Load(int level, void *out, int rowPitch) { diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 110f9df57b..46ee4c58be 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -158,8 +158,13 @@ struct ReplacedTexture { bool Load(int level, void *out, int rowPitch); protected: + void PrepareData(int level); + void PurgeIfOlder(double t); + std::vector levels_; + std::vector> levelData_; ReplacedTextureAlpha alphaStatus_; + double lastUsed_ = 0.0; friend TextureReplacer; }; @@ -193,6 +198,8 @@ public: 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 8661018007..04c6bfae2c 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -724,6 +724,7 @@ void TextureCacheCommon::Decimate(bool forcePressure) { } DecimateVideos(); + replacer_.Decimate(forcePressure); } void TextureCacheCommon::DecimateVideos() { From 045d902525943b2fc6d31b44ad7c5e1895d14e27 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 17 Oct 2021 09:35:11 -0700 Subject: [PATCH 03/12] Replacement: Delay load texture data. --- Core/TextureReplacer.cpp | 169 +++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 70 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index d92fb9e037..0aff1c7793 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -764,7 +764,95 @@ bool ReplacedTexture::IsReady(double budget) { } void ReplacedTexture::PrepareData(int level) { - // TODO + _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); + + const ReplacedTextureLevel &info = levels_[level]; + std::vector &out = levelData_[level]; + + FILE *fp = File::OpenCFile(info.file, "rb"); + if (!fp) { + // Leaving the data sized at zero means failure. + return; + } + + auto imageType = Identify(fp); + if (imageType == ReplacedImageType::ZIM) { + size_t zimSize = File::GetFileSize(fp); + std::unique_ptr zim(new uint8_t[zimSize]); + if (!zim) { + ERROR_LOG(G3D, "Failed to allocate memory for texture replacement"); + fclose(fp); + 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; + } + + int w, h, f; + uint8_t *image; + if (LoadZIMPtr(&zim[0], zimSize, &w, &h, &f, &image)) { + 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(w * h * 4); + ParallelMemcpy(&g_threadManager, &out[0], image, info.w * 4 * info.h); + free(image); + } + + CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)&out[0], w, w, h); + if (res == CHECKALPHA_ANY || level == 0) { + alphaStatus_ = ReplacedTextureAlpha(res); + } + } else if (imageType == ReplacedImageType::PNG) { + png_image png = {}; + png.version = PNG_IMAGE_VERSION; + + 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; + } + if (png.width != info.w || png.height != info.h) { + ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); + fclose(fp); + return; + } + + bool checkedAlpha = false; + if ((png.format & PNG_FORMAT_FLAG_ALPHA) == 0) { + // Well, we know for sure it doesn't have alpha. + if (level == 0) { + alphaStatus_ = ReplacedTextureAlpha::FULL; + } + checkedAlpha = true; + } + png.format = PNG_FORMAT_RGBA; + + out.resize(png.width * png.height * 4); + if (!png_image_finish_read(&png, nullptr, &out[0], png.width * 4, nullptr)) { + ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", info.file.c_str(), png.message); + fclose(fp); + out.resize(0); + return; + } + png_image_free(&png); + + if (!checkedAlpha) { + // This will only check the hashed bits. + CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)&out[0], png.width, png.width, png.height); + if (res == CHECKALPHA_ANY || level == 0) { + alphaStatus_ = ReplacedTextureAlpha(res); + } + } + } + + fclose(fp); } void ReplacedTexture::PurgeIfOlder(double t) { @@ -778,82 +866,23 @@ bool ReplacedTexture::Load(int level, void *out, int rowPitch) { _assert_msg_(out != nullptr && rowPitch > 0, "Invalid out/pitch"); const ReplacedTextureLevel &info = levels_[level]; + const std::vector &data = levelData_[level]; - FILE *fp = File::OpenCFile(info.file, "rb"); - if (!fp) { + if (data.empty()) return false; - } + _assert_msg_(data.size() == info.w * info.h * 4, "Data has wrong size"); - auto imageType = Identify(fp); - if (imageType == ReplacedImageType::ZIM) { - size_t zimSize = File::GetFileSize(fp); - std::unique_ptr zim(new uint8_t[zimSize]); - if (!zim) { - ERROR_LOG(G3D, "Failed to allocate memory for texture replacement"); - fclose(fp); - return false; - } - - 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; - } - - int w, h, f; - uint8_t *image; + if (rowPitch == info.w * 4) { + ParallelMemcpy(&g_threadManager, out, &data[0], info.w * 4 * info.h); + } else { 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); - } - }, 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); - if (res == CHECKALPHA_ANY || level == 0) { - alphaStatus_ = ReplacedTextureAlpha(res); - } - } else if (imageType == ReplacedImageType::PNG) { - png_image png = {}; - png.version = PNG_IMAGE_VERSION; - - 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; - } - - bool checkedAlpha = false; - if ((png.format & PNG_FORMAT_FLAG_ALPHA) == 0) { - // Well, we know for sure it doesn't have alpha. - if (level == 0) { - alphaStatus_ = ReplacedTextureAlpha::FULL; + 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); } - checkedAlpha = true; - } - png.format = PNG_FORMAT_RGBA; - - if (!png_image_finish_read(&png, nullptr, out, rowPitch, nullptr)) { - ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", info.file.c_str(), png.message); - fclose(fp); - return false; - } - 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); - if (res == CHECKALPHA_ANY || level == 0) { - alphaStatus_ = ReplacedTextureAlpha(res); - } - } + }, 0, info.h, MIN_LINES_PER_THREAD); } - fclose(fp); return true; } From 83b7b33cfd565f50a344ecf4d34a45dc11288a1f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 17 Oct 2021 12:36:20 -0700 Subject: [PATCH 04/12] Replacement: Centralize lookup logic. And make sure we don't change our minds about using a replacement during a draw. --- Core/TextureReplacer.cpp | 2 +- Core/TextureReplacer.h | 3 +++ GPU/Common/TextureCacheCommon.cpp | 20 ++++++++++++++++++++ GPU/Common/TextureCacheCommon.h | 1 + GPU/D3D11/TextureCacheD3D11.cpp | 22 ++++++---------------- GPU/Directx9/TextureCacheDX9.cpp | 22 ++++++---------------- GPU/GLES/TextureCacheGLES.cpp | 22 ++++++---------------- GPU/Vulkan/TextureCacheVulkan.cpp | 26 ++++++++------------------ 8 files changed, 51 insertions(+), 67 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 0aff1c7793..56de4ee66c 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -747,7 +747,7 @@ bool ReplacedTexture::IsReady(double budget) { lastUsed_ = time_now_d(); if (levelData_.size() == levels_.size()) - return true; + return Valid(); if (budget <= 0.0) return false; diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 46ee4c58be..a931b9e645 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -195,6 +195,9 @@ public: ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h); bool FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering); + ReplacedTexture &None() { + return none_; + } void NotifyTextureDecoded(const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int w, int h); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 04c6bfae2c..0a931c32b1 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" @@ -1268,6 +1269,25 @@ u32 TextureCacheCommon::EstimateTexMemoryUsage(const TexCacheEntry *entry) { return pixelSize << (dimW + dimH); } +ReplacedTexture &TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int &w, int &h) { + double replaceStart = time_now_d(); + u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; + ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); + if (replaced.IsReady(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; + } + return replacer_.None(); +} + 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 ceb2aa96ff..32ab227b06 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -278,6 +278,7 @@ protected: 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() { diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 1a8b34a54c..199f408e4a 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -485,24 +485,14 @@ 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); - double replaceStart = time_now_d(); - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.IsReady(replacementFrameBudget_ - replacementTimeThisFrame_)) { - if (replaced.GetSize(0, w, h)) { - replacementTimeThisFrame_ += time_now_d() - replaceStart; - - // We're replacing, so we won't scale. - scaleFactor = 1; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; - entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - maxLevel = replaced.MaxLevel(); - badMipSizes = false; - } - } else if (replaced.MaxLevel() >= 0) { - entry->status |= TexCacheEntry::STATUS_TO_REPLACE; + ReplacedTexture &replaced = FindReplacement(entry, w, h); + if (replaced.Valid()) { + // We're replacing, so we won't scale. + scaleFactor = 1; + maxLevel = replaced.MaxLevel(); + badMipSizes = false; } // Don't scale the PPGe texture. diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 20f5c696cc..26f15e6998 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -440,24 +440,14 @@ 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); - double replaceStart = time_now_d(); - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.IsReady(replacementFrameBudget_ - replacementTimeThisFrame_)) { - if (replaced.GetSize(0, w, h)) { - replacementTimeThisFrame_ += time_now_d() - replaceStart; - - // We're replacing, so we won't scale. - scaleFactor = 1; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; - entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - maxLevel = replaced.MaxLevel(); - badMipSizes = false; - } - } else if (replaced.MaxLevel() >= 0) { - entry->status |= TexCacheEntry::STATUS_TO_REPLACE; + ReplacedTexture &replaced = FindReplacement(entry, w, h); + if (replaced.Valid()) { + // We're replacing, so we won't scale. + scaleFactor = 1; + maxLevel = replaced.MaxLevel(); + badMipSizes = false; } // Don't scale the PPGe texture. diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 9a972792c4..b95f6881fa 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -504,24 +504,14 @@ 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); - double replaceStart = time_now_d(); - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.IsReady(replacementFrameBudget_ - replacementTimeThisFrame_)) { - if (replaced.GetSize(0, w, h)) { - replacementTimeThisFrame_ += time_now_d() - replaceStart; - - // We're replacing, so we won't scale. - scaleFactor = 1; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; - entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - maxLevel = replaced.MaxLevel(); - badMipSizes = false; - } - } else if (replaced.MaxLevel() >= 0) { - entry->status |= TexCacheEntry::STATUS_TO_REPLACE; + ReplacedTexture &replaced = FindReplacement(entry, w, h); + if (replaced.Valid()) { + // We're replacing, so we won't scale. + scaleFactor = 1; + maxLevel = replaced.MaxLevel(); + badMipSizes = false; } // Don't scale the PPGe texture. diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 7df7877109..47b2fe8924 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -775,24 +775,14 @@ 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); - double replaceStart = time_now_d(); - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h); - if (replaced.IsReady(replacementFrameBudget_ - replacementTimeThisFrame_)) { - if (replaced.GetSize(0, w, h)) { - replacementTimeThisFrame_ += time_now_d() - replaceStart; - - // We're replacing, so we won't scale. - scaleFactor = 1; - entry->status |= TexCacheEntry::STATUS_IS_SCALED; - entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - maxLevel = replaced.MaxLevel(); - badMipSizes = false; - } - } else if (replaced.MaxLevel() >= 0) { - entry->status |= TexCacheEntry::STATUS_TO_REPLACE; + ReplacedTexture &replaced = FindReplacement(entry, w, h); + if (replaced.Valid()) { + // We're replacing, so we won't scale. + scaleFactor = 1; + maxLevel = replaced.MaxLevel(); + badMipSizes = false; } bool hardwareScaling = g_Config.bTexHardwareScaling && (uploadCS_ != VK_NULL_HANDLE || copyCS_ != VK_NULL_HANDLE); @@ -909,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); @@ -944,7 +934,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { if (replaced.Valid()) { // Directly load the replaced image. data = drawEngine_->GetPushBufferForTextureData()->PushAligned(size, &bufferOffset, &texBuf, pushAlignment); - replaceStart = time_now_d(); + 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); From 0721405628dee69d131fe7dd9e93be1858abfcff Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 17 Oct 2021 12:46:02 -0700 Subject: [PATCH 05/12] Replacement: Avoid clash with X define. --- Core/TextureReplacer.h | 2 +- GPU/Common/TextureCacheCommon.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index a931b9e645..e94a3143cf 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -195,7 +195,7 @@ public: ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h); bool FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering); - ReplacedTexture &None() { + ReplacedTexture &FindNone() { return none_; } diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 0a931c32b1..f40b015211 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1285,7 +1285,7 @@ ReplacedTexture &TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & } else if (replaced.Valid()) { entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } - return replacer_.None(); + return replacer_.FindNone(); } static void ReverseColors(void *dstBuf, const void *srcBuf, GETextureFormat fmt, int numPixels, bool useBGRA) { From ee882d18610454cda7d984b254d91e26b35c3b2e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 17 Oct 2021 13:26:02 -0700 Subject: [PATCH 06/12] Replacement: Avoid rebuild until ready. --- GPU/Common/TextureCacheCommon.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index f40b015211..83c317cada 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -473,9 +473,14 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { reason = "scaling"; } } - if (match && (entry->status & TexCacheEntry::STATUS_TO_REPLACE) && replacementTimeThisFrame_ <= replacementFrameBudget_) { - match = false; - reason = "replacing"; + 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) { @@ -1285,6 +1290,7 @@ ReplacedTexture &TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & } else if (replaced.Valid()) { entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } + replacementTimeThisFrame_ += time_now_d() - replaceStart; return replacer_.FindNone(); } From 09f0578a647087e203026508f7abde41b94adfb3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 21 Oct 2021 13:21:23 -0700 Subject: [PATCH 07/12] Replacement: Use a thread to load tex replacements. --- Core/TextureReplacer.cpp | 106 +++++++++++++++++++++++++----- Core/TextureReplacer.h | 4 ++ GPU/Common/TextureCacheCommon.cpp | 5 +- GPU/Common/TextureCacheCommon.h | 2 +- 4 files changed, 97 insertions(+), 20 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 56de4ee66c..f79ae13730 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -743,24 +743,86 @@ float TextureReplacer::LookupReduceHashRange(int& w, int& h) { } } +class LimitedWaitable : public Waitable { +public: + LimitedWaitable() {} + + void Wait() override { + if (!triggered_) { + std::unique_lock lock(mutex_); + cond_.wait(lock, [&] { return !triggered_; }); + } + } + + bool WaitFor(uint32_t us) { + if (!triggered_) { + 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_; + bool triggered_ = false; +}; + +class ReplacedTextureTask : public Task { +public: + ReplacedTextureTask(ReplacedTexture &tex, LimitedWaitable *w) : tex_(tex), waitable_(w) { + } + + void Run() override { + for (int i = (int)tex_.levelData_.size(); i <= tex_.MaxLevel(); ++i) { + tex_.levelData_.resize(i + 1); + tex_.PrepareData(i); + } + 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_.size() == levels_.size()) return Valid(); if (budget <= 0.0) return false; - double deadline = lastUsed_ + budget; - for (int i = (int)levelData_.size(); i <= MaxLevel(); ++i) { - levelData_.resize(i + 1); - PrepareData(i); - if (time_now_d() >= deadline) { - break; - } + threadWaitable_ = new LimitedWaitable(); + g_threadManager.EnqueueTask(new ReplacedTextureTask(*this, threadWaitable_), TaskType::IO_BLOCKING); + + uint32_t budget_us = (uint32_t)(budget * 1000000.0); + if (threadWaitable_->WaitFor(budget_us)) { + threadWaitable_->WaitAndRelease(); + threadWaitable_ = nullptr; + + // If we finished all the levels, we're done. + return levelData_.size() == levels_.size(); } - // If we finished all the levels, we're done. - return levelData_.size() == levels_.size(); + + // Still pending on thread. + return false; } void ReplacedTexture::PrepareData(int level) { @@ -794,18 +856,26 @@ void ReplacedTexture::PrepareData(int level) { int w, h, f; uint8_t *image; if (LoadZIMPtr(&zim[0], zimSize, &w, &h, &f, &image)) { - if (w != info.w || h != info.h) { + 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(w * h * 4); - ParallelMemcpy(&g_threadManager, &out[0], image, info.w * 4 * info.h); + out.resize(info.w * info.h * 4); + if (w == info.w) { + ParallelMemcpy(&g_threadManager, &out[0], image, info.w * 4 * info.h); + } else { + ParallelRangeLoop(&g_threadManager, [&](int l, int u) { + for (int y = l; y < u; ++y) { + memcpy(&out[info.w * 4 * y], image + w * 4 * y, w * 4); + } + }, 0, h, 4); + } free(image); } - CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)&out[0], w, w, h); + CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)&out[0], info.w, w, h); if (res == CHECKALPHA_ANY || level == 0) { alphaStatus_ = ReplacedTextureAlpha(res); } @@ -818,7 +888,7 @@ void ReplacedTexture::PrepareData(int level) { fclose(fp); return; } - if (png.width != info.w || png.height != info.h) { + 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; @@ -834,8 +904,8 @@ void ReplacedTexture::PrepareData(int level) { } png.format = PNG_FORMAT_RGBA; - out.resize(png.width * png.height * 4); - if (!png_image_finish_read(&png, nullptr, &out[0], png.width * 4, 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); out.resize(0); @@ -845,7 +915,7 @@ void ReplacedTexture::PrepareData(int level) { if (!checkedAlpha) { // This will only check the hashed bits. - CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)&out[0], png.width, 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); } @@ -856,7 +926,7 @@ void ReplacedTexture::PrepareData(int level) { } void ReplacedTexture::PurgeIfOlder(double t) { - if (lastUsed_ < t) { + if (lastUsed_ < t && !threadWaitable_) { levelData_.clear(); } } diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index e94a3143cf..11a0226389 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, @@ -165,8 +167,10 @@ protected: std::vector> levelData_; ReplacedTextureAlpha alphaStatus_; double lastUsed_ = 0.0; + LimitedWaitable * threadWaitable_ = nullptr; friend TextureReplacer; + friend ReplacedTextureTask; }; struct ReplacedTextureDecodeInfo { diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 83c317cada..b694730c47 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1275,10 +1275,13 @@ u32 TextureCacheCommon::EstimateTexMemoryUsage(const TexCacheEntry *entry) { } 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(replacementFrameBudget_ - replacementTimeThisFrame_)) { + if (replaced.IsReady(std::min(MAX_BUDGET_PER_TEX, replacementFrameBudget_ - replacementTimeThisFrame_))) { if (replaced.GetSize(0, w, h)) { replacementTimeThisFrame_ += time_now_d() - replaceStart; diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 32ab227b06..26d6d6d0e6 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -338,7 +338,7 @@ protected: int timesInvalidatedAllThisFrame_ = 0; double replacementTimeThisFrame_ = 0; // TODO: Maybe vary by FPS... - double replacementFrameBudget_ = 0.75 / 60.0; + double replacementFrameBudget_ = 0.5 / 60.0; TexCache cache_; u32 cacheSizeEstimate_ = 0; From c0054dc6cf0e1fe7089fd2b3e28b3647a05ccd29 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 23 Oct 2021 08:06:55 -0700 Subject: [PATCH 08/12] Replacement: Ensurely orderly stop on reset. If the texture is being loaded and we stop or reset, make sure it stops to avoid any crash or hang. --- Core/TextureReplacer.cpp | 10 ++++++++++ Core/TextureReplacer.h | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index f79ae13730..3c7e0c7ec6 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -781,6 +781,8 @@ public: void Run() override { for (int i = (int)tex_.levelData_.size(); i <= tex_.MaxLevel(); ++i) { + if (tex_.cancelPrepare_) + break; tex_.levelData_.resize(i + 1); tex_.PrepareData(i); } @@ -931,6 +933,14 @@ void ReplacedTexture::PurgeIfOlder(double t) { } } +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"); diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 11a0226389..9b9551396c 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -127,6 +127,8 @@ namespace std { } struct ReplacedTexture { + ~ReplacedTexture(); + inline bool Valid() { return !levels_.empty(); } @@ -167,7 +169,8 @@ protected: std::vector> levelData_; ReplacedTextureAlpha alphaStatus_; double lastUsed_ = 0.0; - LimitedWaitable * threadWaitable_ = nullptr; + LimitedWaitable *threadWaitable_ = nullptr; + bool cancelPrepare_ = false; friend TextureReplacer; friend ReplacedTextureTask; From 4c1b5564d2fdff07811478b6e6d7e1f63b65bb88 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 23 Oct 2021 19:58:23 -0700 Subject: [PATCH 09/12] Replacement: Tweak some thread safety. --- Core/TextureReplacer.cpp | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 3c7e0c7ec6..251f97841e 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -17,6 +17,7 @@ #include "ppsspp_config.h" #include +#include #include #include #include @@ -745,7 +746,9 @@ float TextureReplacer::LookupReduceHashRange(int& w, int& h) { class LimitedWaitable : public Waitable { public: - LimitedWaitable() {} + LimitedWaitable() { + triggered_ = false; + } void Wait() override { if (!triggered_) { @@ -771,7 +774,7 @@ public: private: std::condition_variable cond_; std::mutex mutex_; - bool triggered_ = false; + std::atomic triggered_; }; class ReplacedTextureTask : public Task { @@ -780,10 +783,10 @@ public: } void Run() override { - for (int i = (int)tex_.levelData_.size(); i <= tex_.MaxLevel(); ++i) { + tex_.levelData_.resize(tex_.MaxLevel() + 1); + for (int i = 0; i <= tex_.MaxLevel(); ++i) { if (tex_.cancelPrepare_) break; - tex_.levelData_.resize(i + 1); tex_.PrepareData(i); } waitable_->Notify(); @@ -806,8 +809,8 @@ bool ReplacedTexture::IsReady(double budget) { } // Loaded already, or not yet on a thread? - if (levelData_.size() == levels_.size()) - return Valid(); + if (!levelData_.empty()) + return true; if (budget <= 0.0) return false; @@ -820,7 +823,7 @@ bool ReplacedTexture::IsReady(double budget) { threadWaitable_ = nullptr; // If we finished all the levels, we're done. - return levelData_.size() == levels_.size(); + return !levelData_.empty(); } // Still pending on thread. @@ -866,13 +869,11 @@ void ReplacedTexture::PrepareData(int level) { out.resize(info.w * info.h * 4); if (w == info.w) { - ParallelMemcpy(&g_threadManager, &out[0], image, info.w * 4 * info.h); + memcpy(&out[0], image, info.w * 4 * info.h); } else { - ParallelRangeLoop(&g_threadManager, [&](int l, int u) { - for (int y = l; y < u; ++y) { - memcpy(&out[info.w * 4 * y], image + w * 4 * y, w * 4); - } - }, 0, h, 4); + for (int y = 0; y < h; ++y) { + memcpy(&out[info.w * 4 * y], image + w * 4 * y, w * 4); + } } free(image); } @@ -945,6 +946,9 @@ 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]; From fa0e19471c2092be3554467bf28029874b808542 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 23 Oct 2021 20:56:19 -0700 Subject: [PATCH 10/12] Replacement: Add ini setting to disable pop-in. --- Core/Config.cpp | 1 + Core/Config.h | 1 + Core/TextureReplacer.cpp | 37 +++++++++++++++++++++++-------------- Core/TextureReplacer.h | 1 + 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 2afcf76cf5..8143123e88 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 b6bf7f062e..7ded5c9aa0 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -197,6 +197,7 @@ public: 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 251f97841e..ab5c7697e4 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -783,12 +783,7 @@ public: } void Run() override { - tex_.levelData_.resize(tex_.MaxLevel() + 1); - for (int i = 0; i <= tex_.MaxLevel(); ++i) { - if (tex_.cancelPrepare_) - break; - tex_.PrepareData(i); - } + tex_.Prepare(); waitable_->Notify(); } @@ -814,22 +809,36 @@ bool ReplacedTexture::IsReady(double budget) { if (budget <= 0.0) return false; - threadWaitable_ = new LimitedWaitable(); - g_threadManager.EnqueueTask(new ReplacedTextureTask(*this, threadWaitable_), TaskType::IO_BLOCKING); + if (g_Config.bReplaceTexturesAllowLate) { + threadWaitable_ = new LimitedWaitable(); + g_threadManager.EnqueueTask(new ReplacedTextureTask(*this, threadWaitable_), TaskType::IO_BLOCKING); - uint32_t budget_us = (uint32_t)(budget * 1000000.0); - if (threadWaitable_->WaitFor(budget_us)) { - threadWaitable_->WaitAndRelease(); - threadWaitable_ = nullptr; + uint32_t budget_us = (uint32_t)(budget * 1000000.0); + if (threadWaitable_->WaitFor(budget_us)) { + threadWaitable_->WaitAndRelease(); + threadWaitable_ = nullptr; - // If we finished all the levels, we're done. - return !levelData_.empty(); + // 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"); diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 9b9551396c..6deefe8daf 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -162,6 +162,7 @@ struct ReplacedTexture { bool Load(int level, void *out, int rowPitch); protected: + void Prepare(); void PrepareData(int level); void PurgeIfOlder(double t); From abc80f101549d2a71033031b7a112db4f59a2215 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 23 Oct 2021 22:00:51 -0700 Subject: [PATCH 11/12] Replacement: Correct budget on later frames. --- Core/TextureReplacer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index ab5c7697e4..27fcb4e425 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -759,6 +759,8 @@ public: bool WaitFor(uint32_t us) { if (!triggered_) { + if (us == 0) + return false; std::unique_lock lock(mutex_); cond_.wait_for(lock, std::chrono::microseconds(us), [&] { return !triggered_; }); } @@ -795,7 +797,8 @@ private: bool ReplacedTexture::IsReady(double budget) { lastUsed_ = time_now_d(); if (threadWaitable_) { - if (!threadWaitable_->WaitFor(budget)) { + uint32_t budget_us = budget > 0 ? (uint32_t)(budget * 1000000.0) : 0; + if (!threadWaitable_->WaitFor(budget_us)) { return false; } else { threadWaitable_->WaitAndRelease(); From 76186d19191f4256b9d7dc36257db4ad8846fd38 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 23 Oct 2021 22:42:57 -0700 Subject: [PATCH 12/12] Replacement: Allow starting a texture at budget. --- Core/TextureReplacer.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 27fcb4e425..3d40e302d0 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -757,7 +757,8 @@ public: } } - bool WaitFor(uint32_t us) { + bool WaitFor(double budget) { + uint32_t us = budget > 0 ? (uint32_t)(budget * 1000000.0) : 0; if (!triggered_) { if (us == 0) return false; @@ -797,8 +798,7 @@ private: bool ReplacedTexture::IsReady(double budget) { lastUsed_ = time_now_d(); if (threadWaitable_) { - uint32_t budget_us = budget > 0 ? (uint32_t)(budget * 1000000.0) : 0; - if (!threadWaitable_->WaitFor(budget_us)) { + if (!threadWaitable_->WaitFor(budget)) { return false; } else { threadWaitable_->WaitAndRelease(); @@ -809,15 +809,15 @@ bool ReplacedTexture::IsReady(double budget) { // Loaded already, or not yet on a thread? if (!levelData_.empty()) return true; - if (budget <= 0.0) + // 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); - uint32_t budget_us = (uint32_t)(budget * 1000000.0); - if (threadWaitable_->WaitFor(budget_us)) { + if (threadWaitable_->WaitFor(budget)) { threadWaitable_->WaitAndRelease(); threadWaitable_ = nullptr;