mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-18 00:40:30 +02:00
An [indexed] entry set to gpu uploads the indices as an R8 image, and the CLUT8_INDEXED depal shader applies the palette. A texture whose palette changes a lot then costs no CPU time per palette, and keeps one texture instead of one per palette. Once such a replacement is found, the texture is looked up without the CLUT hash, like with the TextureCLUTInShader compat flag. Only the first mip level is used, and the replacer's filtering overrides are skipped since filtering indices mixes up palette entries. Without shader bitwise ops it falls back to coloring on the CPU. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
272 lines
7.2 KiB
C++
272 lines
7.2 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 <mutex>
|
|
#include <string>
|
|
|
|
#include "Common/File/VFS/VFS.h"
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "GPU/Common/ImageCommon.h"
|
|
|
|
class TextureReplacer;
|
|
class LimitedWaitable;
|
|
|
|
// For forward compatibility, we specify the hash.
|
|
enum class ReplacedTextureHash {
|
|
QUICK,
|
|
XXH32,
|
|
XXH64,
|
|
};
|
|
|
|
enum class ReplacedImageType {
|
|
PNG,
|
|
ZIM,
|
|
DDS,
|
|
BASIS, // TODO: Might not even do this, KTX2 is a better container.
|
|
KTX2,
|
|
INVALID,
|
|
};
|
|
|
|
static const int MAX_REPLACEMENT_MIP_LEVELS = 12; // 12 should be plenty, 8 is the max mip levels supported by the PSP.
|
|
|
|
enum class ReplacementState : uint32_t {
|
|
UNLOADED,
|
|
PENDING,
|
|
NOT_FOUND, // Also used on error loading the images.
|
|
ACTIVE,
|
|
CANCEL_INIT,
|
|
COUNT, // Not a valid state
|
|
};
|
|
|
|
const char *StateString(ReplacementState state);
|
|
|
|
struct GPUFormatSupport {
|
|
bool bc123;
|
|
bool astc;
|
|
bool bc7;
|
|
bool etc2;
|
|
};
|
|
|
|
struct ReplacementCacheKey {
|
|
u64 cachekey; // Split into two u32?
|
|
u32 hash;
|
|
|
|
ReplacementCacheKey() : cachekey(0), hash(0) {}
|
|
ReplacementCacheKey(u64 ckey, u32 h) : cachekey(ckey), hash(h) {}
|
|
|
|
bool operator ==(const ReplacementCacheKey &k) const {
|
|
return k.cachekey == cachekey && k.hash == hash;
|
|
}
|
|
|
|
bool operator <(const ReplacementCacheKey &k) const {
|
|
if (k.cachekey == cachekey) {
|
|
return k.hash < hash;
|
|
}
|
|
return k.cachekey < cachekey;
|
|
}
|
|
|
|
// Access the parts.
|
|
u64 CacheKey() const { return cachekey; }
|
|
u32 ClutHash() const { return (u32)(cachekey & 0xFFFFFFFFULL); }
|
|
u32 Address() const { return (u32)(cachekey >> 32); }
|
|
u32 ContentsHash() const { return hash; }
|
|
|
|
void ZeroAddress() {
|
|
cachekey &= 0xFFFFFFFFULL;
|
|
}
|
|
void ZeroClutHash() {
|
|
cachekey &= 0xFFFFFFFF00000000ULL;
|
|
}
|
|
void ZeroContentsHash() {
|
|
hash = 0;
|
|
}
|
|
};
|
|
|
|
struct ReplacementDesc {
|
|
int newW;
|
|
int newH;
|
|
ReplacementCacheKey cacheKey;
|
|
int w;
|
|
int h;
|
|
TextureFiltering forceFiltering;
|
|
std::string hashfiles;
|
|
Path basePath;
|
|
std::vector<std::string> filenames;
|
|
std::string logId;
|
|
GPUFormatSupport formatSupport;
|
|
// The files hold palette indices (palette or grayscale PNG) that the game's own palette colors.
|
|
bool indexed = false;
|
|
// With indexed: the palette is applied by the GPU, in the shader, instead of on the CPU when uploading.
|
|
bool indexedOnGpu = false;
|
|
};
|
|
|
|
class ReplacedTexture;
|
|
|
|
// These aren't actually all replaced, they can also represent a placeholder for a not-found
|
|
// replacement (texture == nullptr).
|
|
struct ReplacedTextureRef {
|
|
ReplacedTexture *texture; // shortcut
|
|
std::string hashfiles; // key into levelCache_
|
|
};
|
|
|
|
// Metadata about a given texture level.
|
|
struct ReplacedTextureLevel {
|
|
// Data dimensions
|
|
int w = 0;
|
|
int h = 0;
|
|
// PSP texture dimensions
|
|
int fullW = 0;
|
|
int fullH = 0;
|
|
|
|
int fullDataSize = 0;
|
|
|
|
// To be able to reload, we need to be able to reopen, unfortunate we can't use zip_file_t.
|
|
// TODO: This really belongs on the level in the cache, not in the individual ReplacedTextureLevel objects.
|
|
VFSFileReference *fileRef = nullptr;
|
|
};
|
|
|
|
class ReplacedTexture {
|
|
public:
|
|
ReplacedTexture(const ReplacedTexture &) = delete;
|
|
ReplacedTexture &operator=(const ReplacedTexture &) = delete;
|
|
ReplacedTexture(VFSBackend *vfs, const ReplacementDesc &desc);
|
|
~ReplacedTexture();
|
|
|
|
inline ReplacementState State() const {
|
|
return state_;
|
|
}
|
|
|
|
void SetState(ReplacementState state) {
|
|
_dbg_assert_(state != state_);
|
|
state_ = state;
|
|
}
|
|
|
|
void GetSize(int level, int *w, int *h) const {
|
|
_dbg_assert_(State() == ReplacementState::ACTIVE);
|
|
_dbg_assert_((size_t)level < levels_.size());
|
|
*w = levels_[level].fullW;
|
|
*h = levels_[level].fullH;
|
|
}
|
|
|
|
int GetLevelDataSizeAfterCopy(int level) const {
|
|
// Includes padding etc.
|
|
return levels_[level].fullDataSize;
|
|
}
|
|
|
|
size_t GetTotalDataSize() const {
|
|
if (State() != ReplacementState::ACTIVE) {
|
|
return 0;
|
|
}
|
|
size_t sz = 0;
|
|
for (auto &data : data_) {
|
|
sz += data.size();
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
bool ForceFiltering(TextureFiltering *forceFiltering) const {
|
|
if (desc_.forceFiltering != (TextureFiltering)0) {
|
|
*forceFiltering = desc_.forceFiltering;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int NumLevels() const {
|
|
_dbg_assert_(State() == ReplacementState::ACTIVE);
|
|
return (int)levels_.size();
|
|
}
|
|
|
|
Draw::DataFormat Format() const {
|
|
_dbg_assert_(State() == ReplacementState::ACTIVE);
|
|
return fmt;
|
|
}
|
|
|
|
const ReplacementDesc &Desc() const {
|
|
return desc_;
|
|
}
|
|
|
|
TextureAlpha AlphaStatus() const {
|
|
return alphaStatus_;
|
|
}
|
|
|
|
bool IsIndexed() const {
|
|
return desc_.indexed;
|
|
}
|
|
|
|
bool IsIndexedOnGpu() const {
|
|
return desc_.indexed && desc_.indexedOnGpu;
|
|
}
|
|
|
|
// Indexed textures only. Size of the stored indices, before padding to the full size.
|
|
void GetIndexDataSize(int level, int *w, int *h) const {
|
|
_dbg_assert_(IsIndexed() && (size_t)level < levels_.size());
|
|
*w = levels_[level].w;
|
|
*h = levels_[level].h;
|
|
}
|
|
// Indexed textures only. Copies the indices, one byte each, tightly packed.
|
|
bool CopyIndexLevelTo(int level, uint8_t *out, size_t outDataSize);
|
|
// Indexed textures only. Copies the indices into an R8 image of the full size, one row per rowPitch bytes,
|
|
// and zeroes the padding.
|
|
bool CopyIndexLevelToPitched(int level, uint8_t *out, size_t outDataSize, int rowPitch);
|
|
|
|
bool Poll(double budget);
|
|
bool CopyLevelTo(int level, uint8_t *out, size_t outDataSize, int rowPitch);
|
|
|
|
std::string logId_;
|
|
|
|
private:
|
|
enum class LoadLevelResult {
|
|
LOAD_ERROR = 0,
|
|
CONTINUE = 1,
|
|
DONE = 2,
|
|
};
|
|
|
|
void Prepare(VFSBackend *vfs);
|
|
// Waits for any load task, then drops all data and file references, and detaches from the VFS.
|
|
// The texture reloads from scratch once it gets a VFS again. Must run before the VFS is deleted.
|
|
void Unload();
|
|
void ReleaseLevels();
|
|
LoadLevelResult LoadLevelData(VFSFileReference *fileRef, const std::string &filename, int level, Draw::DataFormat *pixelFormat);
|
|
void PurgeIfNotUsedSinceTime(double t);
|
|
|
|
std::vector<std::vector<uint8_t>> data_;
|
|
std::vector<ReplacedTextureLevel> levels_;
|
|
// Image format of mip level 0; mixing formats across levels is not
|
|
// allowed (container formats like KTX2/DDS manage their own mip chain).
|
|
ReplacedImageType firstImageType_ = ReplacedImageType::INVALID;
|
|
|
|
double lastUsed_ = 0.0;
|
|
LimitedWaitable *threadWaitable_ = nullptr;
|
|
std::mutex lock_;
|
|
Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED;
|
|
TextureAlpha alphaStatus_ = TextureAlpha::Any;
|
|
double lastUsed = 0.0;
|
|
|
|
std::atomic<ReplacementState> state_ = ReplacementState::UNLOADED;
|
|
|
|
VFSBackend *vfs_ = nullptr;
|
|
ReplacementDesc desc_;
|
|
|
|
friend class TextureReplacer;
|
|
friend class ReplacedTextureTask;
|
|
};
|