mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Merge pull request #14230 from unknownbrackets/texreplace
Support texture replacement filtering overrides
This commit is contained in:
commit
0facd4d4a6
12 changed files with 148 additions and 74 deletions
|
@ -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);
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Common/CommonWindows.h"
|
||||
#include <Windowsx.h>
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace MainWindow {
|
||||
void MainWindowMenu_Process(HWND hWnd, WPARAM wParam);
|
||||
|
|
Loading…
Add table
Reference in a new issue