From fb3ad1df4b758c752baf229aae74bb5c7569e2e5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 27 Feb 2021 17:16:16 -0800 Subject: [PATCH 1/3] Replacement: Read in texture filtering overrides. If you're replacing, you can know more information about linear safety for tests. --- Core/TextureReplacer.cpp | 129 +++++++++++++++++++++++--------- Core/TextureReplacer.h | 4 + GPU/Common/TextureCacheCommon.h | 7 +- GPU/Common/TextureDecoder.h | 6 ++ GPU/Software/Rasterizer.cpp | 1 - Windows/MainWindowMenu.cpp | 2 +- 6 files changed, 105 insertions(+), 44 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 3b9aeb99e4b2..517c799d656f 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -76,6 +76,7 @@ bool TextureReplacer::LoadIni() { hash_ = ReplacedTextureHash::QUICK; aliases_.clear(); hashranges_.clear(); + filtering_.clear(); allowVideo_ = false; ignoreAddress_ = false; @@ -183,6 +184,14 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { } } + if (ini.HasSection("filtering")) { + auto filters = ini.GetOrCreateSection("filtering")->ToMap(); + // Format: hashname = nearest or linear + for (const auto &item : filters) { + ParseFiltering(item.first, item.second); + } + } + return true; } @@ -221,6 +230,23 @@ void TextureReplacer::ParseHashRange(const std::string &key, const std::string & hashranges_[rangeKey] = WidthHeightPair(toW, toH); } +void TextureReplacer::ParseFiltering(const std::string &key, const std::string &value) { + ReplacementCacheKey itemKey(0, 0); + if (sscanf(key.c_str(), "%16llx%8x", &itemKey.cachekey, &itemKey.hash) >= 1) { + if (!strcasecmp(value.c_str(), "nearest")) { + filtering_[itemKey] = TEX_FILTER_FORCE_NEAREST; + } else if (!strcasecmp(value.c_str(), "linear")) { + filtering_[itemKey] = TEX_FILTER_FORCE_LINEAR; + } else if (!strcasecmp(value.c_str(), "auto")) { + filtering_[itemKey] = TEX_FILTER_AUTO; + } else { + ERROR_LOG(G3D, "Unsupported syntax under [filtering]: %s", value.c_str()); + } + } else { + ERROR_LOG(G3D, "Unsupported syntax under [filtering]: %s", key.c_str()); + } +} + u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV) { _dbg_assert_msg_(enabled_, "Replacement not enabled"); @@ -503,45 +529,74 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl savedCache_[replacementKey] = saved; } -std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, int level) { - ReplacementAliasKey key(cachekey, hash, level); - auto alias = aliases_.find(key); - if (alias == aliases_.end()) { - // Also check for a few more aliases with zeroed portions: - // Only clut hash (very dangerous in theory, in practice not more than missing "just" data hash) - key.cachekey = cachekey & 0xFFFFFFFFULL; +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); + if (alias != map.end()) + return alias; + + // Also check for a few more aliases with zeroed portions: + // Only clut hash (very dangerous in theory, in practice not more than missing "just" data hash) + key.cachekey = cachekey & 0xFFFFFFFFULL; + key.hash = 0; + alias = map.find(key); + if (alias != map.end()) + return alias; + + if (!ignoreAddress) { + // No data hash. + key.cachekey = cachekey; key.hash = 0; - alias = aliases_.find(key); - - if (!ignoreAddress_ && alias == aliases_.end()) { - // No data hash. - key.cachekey = cachekey; - key.hash = 0; - alias = aliases_.find(key); - } - - if (alias == aliases_.end()) { - // No address. - key.cachekey = cachekey & 0xFFFFFFFFULL; - key.hash = hash; - alias = aliases_.find(key); - } + alias = map.find(key); + if (alias != map.end()) + return alias; + } + + // No address. + key.cachekey = cachekey & 0xFFFFFFFFULL; + key.hash = hash; + alias = map.find(key); + if (alias != map.end()) + return alias; + + if (!ignoreAddress) { + // Address, but not clut hash (in case of garbage clut data.) + key.cachekey = cachekey & ~0xFFFFFFFFULL; + key.hash = hash; + alias = map.find(key); + if (alias != map.end()) + return alias; + } + + // Anything with this data hash (a little dangerous.) + key.cachekey = 0; + key.hash = hash; + return map.find(key); +} - if (!ignoreAddress_ && alias == aliases_.end()) { - // Address, but not clut hash (in case of garbage clut data.) - key.cachekey = cachekey & ~0xFFFFFFFFULL; - key.hash = hash; - alias = aliases_.find(key); - } +bool TextureReplacer::FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering) { + if (!Enabled() || !g_Config.bReplaceTextures) { + return false; + } - if (alias == aliases_.end()) { - // Anything with this data hash (a little dangerous.) - key.cachekey = 0; - key.hash = hash; - alias = aliases_.find(key); - } + ReplacementCacheKey replacementKey(cachekey, hash); + auto filter = LookupWildcard(filtering_, replacementKey, cachekey, hash, ignoreAddress_); + if (filter == filtering_.end()) { + // Allow a global wildcard. + replacementKey.cachekey = 0; + replacementKey.hash = 0; + filter = filtering_.find(replacementKey); + } + if (filter != filtering_.end()) { + *forceFiltering = filter->second; + return true; } + return false; +} +std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, int level) { + ReplacementAliasKey key(cachekey, hash, level); + auto alias = LookupWildcard(aliases_, key, cachekey, hash, ignoreAddress_); if (alias != aliases_.end()) { // Note: this will be blank if explicitly ignored. return alias->second; @@ -649,13 +704,15 @@ bool TextureReplacer::GenerateIni(const std::string &gameID, std::string *genera fs << "[games]\n"; fs << "# Used to make it easier to install, and override settings for other regions.\n"; fs << "# Files still have to be copied to each TEXTURES folder."; - fs << gameID << " = textures.ini\n"; + fs << gameID << " = " << INI_FILENAME << "\n"; fs << "\n"; + fs << "[hashes]\n"; fs << "# Use / for folders not \\, avoid special characters, and stick to lowercase.\n"; fs << "# See wiki for more info.\n"; - fs << "[hashes]\n"; fs << "\n"; fs << "[hashranges]\n"; + fs << "\n"; + fs << "[filtering]\n"; fs.close(); } return File::Exists(texturesDirectory + INI_FILENAME); diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index d90f373c4ecc..46d67037ed65 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -23,6 +23,7 @@ #include #include "Common/Common.h" #include "Common/MemoryUtil.h" +#include "GPU/Common/TextureDecoder.h" #include "GPU/ge_constants.h" class IniFile; @@ -183,6 +184,7 @@ class TextureReplacer { u32 ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV); ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h); + bool FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering); void NotifyTextureDecoded(const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int w, int h); @@ -192,6 +194,7 @@ class TextureReplacer { bool LoadIni(); bool LoadIniValues(IniFile &ini, bool isOverride = false); void ParseHashRange(const std::string &key, const std::string &value); + void ParseFiltering(const std::string &key, const std::string &value); bool LookupHashRange(u32 addr, int &w, int &h); std::string LookupHashFile(u64 cachekey, u32 hash, int level); std::string HashName(u64 cachekey, u32 hash, int level); @@ -209,6 +212,7 @@ class TextureReplacer { typedef std::pair WidthHeightPair; std::unordered_map hashranges_; std::unordered_map aliases_; + std::unordered_map filtering_; ReplacedTexture none_; std::unordered_map cache_; diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 809921d790e8..79eac7be9fc8 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -28,12 +28,6 @@ #include "GPU/Common/GPUDebugInterface.h" #include "GPU/Common/TextureDecoder.h" -enum TextureFiltering { - TEX_FILTER_AUTO = 1, - TEX_FILTER_FORCE_NEAREST = 2, - TEX_FILTER_FORCE_LINEAR = 3, -}; - enum FramebufferNotification { NOTIFY_FB_CREATED, NOTIFY_FB_UPDATED, @@ -53,6 +47,7 @@ enum FramebufferNotificationChannel { #define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame struct VirtualFramebuffer; +class TextureReplacer; namespace Draw { class DrawContext; diff --git a/GPU/Common/TextureDecoder.h b/GPU/Common/TextureDecoder.h index 02e7939cdc21..b00958a29667 100644 --- a/GPU/Common/TextureDecoder.h +++ b/GPU/Common/TextureDecoder.h @@ -31,6 +31,12 @@ enum CheckAlphaResult { #include "GPU/Common/TextureDecoderNEON.h" #include "GPU/GPUState.h" +enum TextureFiltering { + TEX_FILTER_AUTO = 1, + TEX_FILTER_FORCE_NEAREST = 2, + TEX_FILTER_FORCE_LINEAR = 3, +}; + void SetupTextureDecoder(); // Pitch must be aligned to 16 bits (as is the case on a PSP) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 1f3996398c32..dfc0eea42026 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -27,7 +27,6 @@ #include "Core/Reporting.h" #include "GPU/GPUState.h" -#include "GPU/Common/TextureCacheCommon.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/Software/SoftGpu.h" #include "GPU/Software/Rasterizer.h" diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 567635ffe5a6..a5711a94eb6a 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -28,7 +28,7 @@ #include "UI/OnScreenDisplay.h" #include "GPU/Common/PostShader.h" #include "GPU/Common/FramebufferManagerCommon.h" -#include "GPU/Common/TextureCacheCommon.h" +#include "GPU/Common/TextureDecoder.h" #include "GPU/Common/TextureScalerCommon.h" #include "Core/Config.h" From 82a7a264096560c6f1e244712bb8e091532a4f1d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 27 Feb 2021 17:17:21 -0800 Subject: [PATCH 2/3] GPU: Look up replaced filtering options. --- GPU/Common/TextureCacheCommon.cpp | 58 +++++++++++++++++++------------ GPU/Common/TextureCacheCommon.h | 2 +- GPU/D3D11/TextureCacheD3D11.cpp | 2 +- GPU/Directx9/TextureCacheDX9.cpp | 2 +- GPU/GLES/TextureCacheGLES.cpp | 2 +- GPU/Vulkan/TextureCacheVulkan.cpp | 2 +- 6 files changed, 40 insertions(+), 28 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 180cbfa52745..47dcf6d7557a 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -147,7 +147,7 @@ static int TexLog2(float delta) { return useful - 127 * 256; } -SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, u32 texAddr) { +SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCacheEntry *entry) { SamplerCacheKey key; int minFilt = gstate.texfilter & 0x7; @@ -214,37 +214,49 @@ SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, u32 texAddr) } // Video bilinear override - if (!key.magFilt && texAddr != 0 && IsVideo(texAddr)) { + if (!key.magFilt && entry != nullptr && IsVideo(entry->addr)) { // Enforce bilinear filtering on magnification. key.magFilt = 1; } - // Filtering overrides - switch (g_Config.iTexFiltering) { - case TEX_FILTER_AUTO: - // Follow what the game wants. We just do a single heuristic change to avoid bleeding of wacky color test colors - // in higher resolution (used by some games for sprites, and they accidentally have linear filter on). - if (gstate.isModeThrough() && g_Config.iInternalResolution != 1) { - bool uglyColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && gstate.getColorTestRef() != 0; - if (uglyColorTest) { - // Force to nearest. - key.magFilt = 0; - key.minFilt = 0; + // Filtering overrides from replacements or settings. + TextureFiltering forceFiltering = TEX_FILTER_AUTO; + u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; + if (!replacer_.Enabled() || !replacer_.FindFiltering(cachekey, entry->fullhash, &forceFiltering)) { + switch (g_Config.iTexFiltering) { + case TEX_FILTER_AUTO: + // Follow what the game wants. We just do a single heuristic change to avoid bleeding of wacky color test colors + // in higher resolution (used by some games for sprites, and they accidentally have linear filter on). + if (gstate.isModeThrough() && g_Config.iInternalResolution != 1) { + bool uglyColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && gstate.getColorTestRef() != 0; + if (uglyColorTest) + forceFiltering = TEX_FILTER_FORCE_NEAREST; + } + break; + case TEX_FILTER_FORCE_LINEAR: + // Override to linear filtering if there's no alpha or color testing going on. + if ((!gstate.isColorTestEnabled() || IsColorTestTriviallyTrue()) && + (!gstate.isAlphaTestEnabled() || IsAlphaTestTriviallyTrue())) { + forceFiltering = TEX_FILTER_FORCE_LINEAR; } + break; + case TEX_FILTER_FORCE_NEAREST: + default: + // Just force to nearest without checks. Safe (but ugly). + forceFiltering = TEX_FILTER_FORCE_NEAREST; + break; } + } + + switch (forceFiltering) { + case TEX_FILTER_AUTO: break; case TEX_FILTER_FORCE_LINEAR: - // Override to linear filtering if there's no alpha or color testing going on. - if ((!gstate.isColorTestEnabled() || IsColorTestTriviallyTrue()) && - (!gstate.isAlphaTestEnabled() || IsAlphaTestTriviallyTrue())) { - key.magFilt = 1; - key.minFilt = 1; - key.mipFilt = 1; - } + key.magFilt = 1; + key.minFilt = 1; + key.mipFilt = 1; break; case TEX_FILTER_FORCE_NEAREST: - default: - // Just force to nearest without checks. Safe (but ugly). key.magFilt = 0; key.minFilt = 0; break; @@ -254,7 +266,7 @@ SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, u32 texAddr) } SamplerCacheKey TextureCacheCommon::GetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight) { - SamplerCacheKey key = GetSamplingParams(0, 0); + SamplerCacheKey key = GetSamplingParams(0, nullptr); // Kill any mipmapping settings. key.mipEnable = false; diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 79eac7be9fc8..9b16ade56611 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -285,7 +285,7 @@ class TextureCacheCommon { u32 EstimateTexMemoryUsage(const TexCacheEntry *entry); - SamplerCacheKey GetSamplingParams(int maxLevel, u32 texAddr); + SamplerCacheKey GetSamplingParams(int maxLevel, const TexCacheEntry *entry); SamplerCacheKey GetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight); void UpdateMaxSeenV(TexCacheEntry *entry, bool throughMode); diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index c96b56e61a88..24f20071a549 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -225,7 +225,7 @@ void TextureCacheD3D11::BindTexture(TexCacheEntry *entry) { lastBoundTexture = textureView; } int maxLevel = (entry->status & TexCacheEntry::STATUS_BAD_MIPS) ? 0 : entry->maxLevel; - SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry->addr); + SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry); ID3D11SamplerState *state = samplerCache_.GetOrCreateSampler(device_, samplerKey); context_->PSSetSamplers(0, 1, &state); } diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 8b8032121a06..0b1d536ed7f9 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -196,7 +196,7 @@ void TextureCacheDX9::BindTexture(TexCacheEntry *entry) { lastBoundTexture = texture; } int maxLevel = (entry->status & TexCacheEntry::STATUS_BAD_MIPS) ? 0 : entry->maxLevel; - SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry->addr); + SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry); ApplySamplingParams(samplerKey); } diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 20a96ff4bc19..e15b900acad2 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -228,7 +228,7 @@ void TextureCacheGLES::BindTexture(TexCacheEntry *entry) { lastBoundTexture = entry->textureName; } int maxLevel = (entry->status & TexCacheEntry::STATUS_BAD_MIPS) ? 0 : entry->maxLevel; - SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry->addr); + SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry); ApplySamplingParams(samplerKey); gstate_c.SetUseShaderDepal(false); } diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 64a76d230373..31ea90b56528 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -519,7 +519,7 @@ void TextureCacheVulkan::BindTexture(TexCacheEntry *entry) { entry->vkTex->Touch(); imageView_ = entry->vkTex->GetImageView(); int maxLevel = (entry->status & TexCacheEntry::STATUS_BAD_MIPS) ? 0 : entry->maxLevel; - SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry->addr); + SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry); curSampler_ = samplerCache_.GetOrCreateSampler(samplerKey); drawEngine_->SetDepalTexture(VK_NULL_HANDLE); gstate_c.SetUseShaderDepal(false); From 0f5ebd210a40f349531e0383a1c67fa6ccb13992 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 28 Feb 2021 07:51:38 -0800 Subject: [PATCH 3/3] Windows: Add missing header for menu. --- Windows/MainWindowMenu.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Windows/MainWindowMenu.h b/Windows/MainWindowMenu.h index 26bdfc608efe..5669335c228e 100644 --- a/Windows/MainWindowMenu.h +++ b/Windows/MainWindowMenu.h @@ -2,6 +2,7 @@ #include "Common/CommonWindows.h" #include +#include "Core/System.h" namespace MainWindow { void MainWindowMenu_Process(HWND hWnd, WPARAM wParam);