Merge pull request #14230 from unknownbrackets/texreplace

Support texture replacement filtering overrides
This commit is contained in:
Henrik Rydgård 2021-02-28 18:09:38 +01:00 committed by GitHub
commit 0facd4d4a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 148 additions and 74 deletions

View file

@ -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 <typename Key, typename Value>
static typename std::unordered_map<Key, Value>::const_iterator LookupWildcard(const std::unordered_map<Key, Value> &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);
}
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);
}
if (alias == aliases_.end()) {
// Anything with this data hash (a little dangerous.)
key.cachekey = 0;
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);
}
bool TextureReplacer::FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering) {
if (!Enabled() || !g_Config.bReplaceTextures) {
return false;
}
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);

View file

@ -23,6 +23,7 @@
#include <vector>
#include "Common/Common.h"
#include "Common/MemoryUtil.h"
#include "GPU/Common/TextureDecoder.h"
#include "GPU/ge_constants.h"
class IniFile;
@ -183,6 +184,7 @@ public:
u32 ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV);
ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h);
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 @@ protected:
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 @@ protected:
typedef std::pair<int, int> WidthHeightPair;
std::unordered_map<u64, WidthHeightPair> hashranges_;
std::unordered_map<ReplacementAliasKey, std::string> aliases_;
std::unordered_map<ReplacementCacheKey, TextureFiltering> filtering_;
ReplacedTexture none_;
std::unordered_map<ReplacementCacheKey, ReplacedTexture> cache_;

View file

@ -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;

View file

@ -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;
@ -290,7 +285,7 @@ protected:
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);

View file

@ -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)

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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"

View file

@ -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);

View file

@ -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"

View file

@ -2,6 +2,7 @@
#include "Common/CommonWindows.h"
#include <Windowsx.h>
#include "Core/System.h"
namespace MainWindow {
void MainWindowMenu_Process(HWND hWnd, WPARAM wParam);