Files
ppsspp/GPU/Common/TextureReplacer.h
T
Claude 03391c66cb Texture replacement: Support palette-indexed replacement images
- Fixes #15145

A replacement listed in a new [indexed] section of textures.ini holds
palette indices instead of colors: a palette PNG (the file's palette index)
or an 8-bit grayscale PNG (the gray value), at any resolution. When the
texture is built, the indices go through the game's current CLUT, including
the CLUT index shift, mask and start position, and the result is uploaded as
an ordinary RGBA texture. A game that swaps or customizes a palette keeps
working with a high-resolution replacement.

[indexed] keys use the [hashes] format and are matched with the same
wildcards, so a key with a zero CLUT hash covers every palette of the
texture. The contents hash only covers the indices, so one image is shared
by all palettes (the loaded data is shared, each palette still has its own
texture cache entry).

Saving does not dump indexed textures yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 21:42:39 +02:00

169 lines
5.8 KiB
C++

// Copyright (c) 2016- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "ppsspp_config.h"
#include <mutex>
#include <string>
#include <unordered_map>
#include <map>
#include <vector>
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
#include "Common/MemoryUtil.h"
#include "Common/File/Path.h"
#include "Common/File/VFS/VFS.h"
#include "Common/GPU/DataFormat.h"
#include "GPU/Common/TextureDecoder.h"
#include "GPU/Common/ReplacedTexture.h"
#include "GPU/ge_constants.h"
class IniFile;
class TextureCacheCommon;
class TextureReplacer;
class ReplacedTextureTask;
class LimitedWaitable;
class VFSBackend;
struct SavedTextureCacheData {
int levelW[8]{};
int levelH[8]{};
bool levelSaved[8]{};
double lastTimeSaved = 0.0;
};
namespace std {
template <>
struct hash<ReplacementCacheKey> {
size_t operator()(const ReplacementCacheKey &k) const {
return std::hash<u64>()(k.cachekey ^ ((u64)k.hash << 32));
}
};
}
struct ReplacedTextureDecodeInfo {
u64 cachekey;
u32 hash;
u32 addr;
bool isVideo;
bool isFinal;
Draw::DataFormat fmt;
};
enum class ReplacerDecimateMode {
NEW_FRAME,
FORCE_PRESSURE,
ALL,
};
class TextureReplacer {
public:
TextureReplacer(const TextureReplacer &) = delete;
TextureReplacer &operator=(const TextureReplacer &) = delete;
// The draw context is checked for supported texture formats.
TextureReplacer(Draw::DrawContext *draw);
~TextureReplacer();
void NotifyConfigChanged();
bool Enabled() const { return replaceEnabled_ || saveEnabled_; } // used to check hashing method etc.
bool ReplaceEnabled() const { return replaceEnabled_; }
bool SaveEnabled() const { return saveEnabled_; }
u32 ComputeHash(u32 addr, int bufw, int w, int h, bool swizzled, GETextureFormat fmt, u16 maxSeenV);
// Returns nullptr if not found.
ReplacedTexture *FindReplacement(ReplacementCacheKey key, int w, int h);
// For testing: point the replacer at a texture pack directory and load its
// ini, without touching the global config. Returns true on success.
bool LoadPackForTesting(const Path &basePath, std::string *error);
// Check if a NotifyTextureDecoded for this texture is desired (used to avoid reads from write-combined memory.)
bool WillSave(const ReplacedTextureDecodeInfo &replacedInfo) const;
// Notify that a new texture was decoded. May already be upscaled, saves the data passed.
// If the replacer knows about this one already, texture will be passed in, otherwise nullptr.
void NotifyTextureDecoded(ReplacedTexture *texture, const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int origW, int origH, int scaledW, int scaledH);
void Decimate(ReplacerDecimateMode mode);
static bool GenerateIni(const std::string &gameID, Path &generatedFilename);
static bool IniExists(const std::string &gameID);
int GetNumTrackedTextures() const { return (int)cache_.size(); }
int GetNumCachedReplacedTextures() const { return (int)levelCache_.size(); }
static std::string HashName(ReplacementCacheKey key, int level);
protected:
bool FindFiltering(ReplacementCacheKey key, TextureFiltering *forceFiltering);
bool IsIndexed(ReplacementCacheKey key) const;
bool LoadIni(std::string *error, bool notify = true);
void DeleteVFS();
bool LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverride, std::string *error);
void ParseHashRange(const std::string &key, const std::string &value);
void ParseFiltering(const std::string &key, const std::string &value);
void ParseIndexed(const std::string &key, const std::string &value);
void ParseReduceHashRange(const std::string& key, const std::string& value);
bool LookupHashRange(u32 addr, int w, int h, int *newW, int *newH);
float LookupReduceHashRange(int w, int h);
std::string LookupHashFile(ReplacementCacheKey key, bool *foundAlias, bool *ignored);
bool replaceEnabled_ = false;
bool saveEnabled_ = false;
bool ignoreAddress_ = false;
bool reduceHash_ = false;
bool ignoreMipmap_ = false;
int skipLastDXT1Blocks128x64_ = 0;
int skipLastDXT1Blocks128x128_ = 0;
float reduceHashGlobalValue = 0.5f; // Global value for textures dump pngs of all sizes, 0.5 by default but can be set in textures.ini
double lastTextureCacheSizeGB_ = 0.0;
std::string gameID_;
Path basePath_;
Path newTextureDir_;
ReplacedTextureHash textureHash_ = ReplacedTextureHash::QUICK;
VFSBackend *vfs_ = nullptr;
bool vfsIsZip_ = false;
GPUFormatSupport formatSupport_{};
typedef std::pair<int, int> WidthHeightPair;
std::unordered_map<u64, WidthHeightPair> hashranges_;
std::unordered_map<u64, float> reducehashranges_;
std::unordered_map<ReplacementCacheKey, std::string> aliases_;
std::unordered_map<ReplacementCacheKey, TextureFiltering> filtering_;
// Replacements that hold palette indices instead of colors, from the [indexed] section.
std::unordered_map<ReplacementCacheKey, bool> indexed_;
std::unordered_map<ReplacementCacheKey, ReplacedTextureRef> cache_;
std::unordered_map<ReplacementCacheKey, SavedTextureCacheData> savedCache_;
// the key is either from aliases_, in which case it's a |-separated sequence of texture filenames of the levels of a texture.
// alternatively the key is from the generated texture filename.
std::unordered_map<std::string, ReplacedTexture *> levelCache_;
};