Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support texture replacement filtering overrides #14230

Merged
merged 3 commits into from
Feb 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 93 additions & 36 deletions Core/TextureReplacer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ bool TextureReplacer::LoadIni() {
hash_ = ReplacedTextureHash::QUICK;
aliases_.clear();
hashranges_.clear();
filtering_.clear();

allowVideo_ = false;
ignoreAddress_ = false;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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);
}
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;
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions Core/TextureReplacer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand All @@ -209,6 +212,7 @@ class TextureReplacer {
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_;
Expand Down
58 changes: 35 additions & 23 deletions GPU/Common/TextureCacheCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
9 changes: 2 additions & 7 deletions GPU/Common/TextureCacheCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -53,6 +47,7 @@ enum FramebufferNotificationChannel {
#define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame

struct VirtualFramebuffer;
class TextureReplacer;

namespace Draw {
class DrawContext;
Expand Down Expand Up @@ -290,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);

Expand Down
6 changes: 6 additions & 0 deletions GPU/Common/TextureDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion GPU/D3D11/TextureCacheD3D11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion GPU/Directx9/TextureCacheDX9.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion GPU/GLES/TextureCacheGLES.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 0 additions & 1 deletion GPU/Software/Rasterizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion GPU/Vulkan/TextureCacheVulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion Windows/MainWindowMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions Windows/MainWindowMenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "Common/CommonWindows.h"
#include <Windowsx.h>
#include "Core/System.h"

namespace MainWindow {
void MainWindowMenu_Process(HWND hWnd, WPARAM wParam);
Expand Down