diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d78af8512a0..254acf3daa3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1432,6 +1432,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Screenshot.h Core/System.cpp Core/System.h + Core/TextureReplacer.cpp + Core/TextureReplacer.h Core/Util/AudioFormat.cpp Core/Util/AudioFormat.h Core/Util/GameManager.cpp diff --git a/Common/ColorConv.cpp b/Common/ColorConv.cpp index a318fedd868b..9dcd1876f98c 100644 --- a/Common/ColorConv.cpp +++ b/Common/ColorConv.cpp @@ -435,6 +435,39 @@ void ConvertRGBA4444ToRGBA8888(u32 *dst32, const u16 *src, const u32 numPixels) } } +void ConvertABGR565ToRGBA8888(u32 *dst32, const u16 *src, const u32 numPixels) { + u8 *dst = (u8 *)dst32; + for (u32 x = 0; x < numPixels; x++) { + u16 col = src[x]; + dst[x * 4] = Convert5To8((col >> 11) & 0x1f); + dst[x * 4 + 1] = Convert6To8((col >> 5) & 0x3f); + dst[x * 4 + 2] = Convert5To8((col) & 0x1f); + dst[x * 4 + 3] = 255; + } +} + +void ConvertABGR1555ToRGBA8888(u32 *dst32, const u16 *src, const u32 numPixels) { + u8 *dst = (u8 *)dst32; + for (u32 x = 0; x < numPixels; x++) { + u16 col = src[x]; + dst[x * 4] = Convert5To8((col >> 11) & 0x1f); + dst[x * 4 + 1] = Convert5To8((col >> 6) & 0x1f); + dst[x * 4 + 2] = Convert5To8((col >> 1) & 0x1f); + dst[x * 4 + 3] = (col & 1) ? 255 : 0; + } +} + +void ConvertABGR4444ToRGBA8888(u32 *dst32, const u16 *src, const u32 numPixels) { + u8 *dst = (u8 *)dst32; + for (u32 x = 0; x < numPixels; x++) { + u16 col = src[x]; + dst[x * 4] = Convert4To8(col >> 12); + dst[x * 4 + 1] = Convert4To8((col >> 8) & 0xf); + dst[x * 4 + 2] = Convert4To8((col >> 4) & 0xf); + dst[x * 4 + 3] = Convert4To8(col & 0xf); + } +} + void ConvertRGBA4444ToBGRA8888(u32 *dst32, const u16 *src, const u32 numPixels) { u8 *dst = (u8 *)dst32; for (u32 x = 0; x < numPixels; x++) { diff --git a/Common/ColorConv.h b/Common/ColorConv.h index 5a3b71fb8495..5750922ba20e 100644 --- a/Common/ColorConv.h +++ b/Common/ColorConv.h @@ -126,6 +126,10 @@ void ConvertRGBA565ToRGBA8888(u32 *dst, const u16 *src, const u32 numPixels); void ConvertRGBA5551ToRGBA8888(u32 *dst, const u16 *src, const u32 numPixels); void ConvertRGBA4444ToRGBA8888(u32 *dst, const u16 *src, const u32 numPixels); +void ConvertABGR565ToRGBA8888(u32 *dst, const u16 *src, const u32 numPixels); +void ConvertABGR1555ToRGBA8888(u32 *dst, const u16 *src, const u32 numPixels); +void ConvertABGR4444ToRGBA8888(u32 *dst, const u16 *src, const u32 numPixels); + void ConvertRGBA4444ToBGRA8888(u32 *dst, const u16 *src, const u32 numPixels); void ConvertRGBA5551ToBGRA8888(u32 *dst, const u16 *src, const u32 numPixels); void ConvertRGB565ToBGRA8888(u32 *dst, const u16 *src, const u32 numPixels); diff --git a/Core/Config.cpp b/Core/Config.cpp index e66ac808c6ff..a5b4bb79d557 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -477,8 +477,9 @@ static ConfigSetting graphicsSettings[] = { ConfigSetting("ImmersiveMode", &g_Config.bImmersiveMode, false, true, true), ReportedConfigSetting("TrueColor", &g_Config.bTrueColor, true, true, true), - ReportedConfigSetting("MipMap", &g_Config.bMipMap, true, true, true), + ReportedConfigSetting("ReplaceTextures", &g_Config.bReplaceTextures, true, true, true), + ReportedConfigSetting("SaveNewTextures", &g_Config.bSaveNewTextures, false, true, true), ReportedConfigSetting("TexScalingLevel", &g_Config.iTexScalingLevel, 1, true, true), ReportedConfigSetting("TexScalingType", &g_Config.iTexScalingType, 0, true, true), diff --git a/Core/Config.h b/Core/Config.h index 8618adaa461a..b2cfc1351e45 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -180,6 +180,8 @@ struct Config { int bHighQualityDepth; bool bTrueColor; bool bMipMap; + bool bReplaceTextures; + bool bSaveNewTextures; int iTexScalingLevel; // 1 = off, 2 = 2x, ..., 5 = 5x int iTexScalingType; // 0 = xBRZ, 1 = Hybrid bool bTexDeposterize; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 514cf98e748d..6f255870a575 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -181,6 +181,7 @@ + @@ -506,6 +507,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 76cce1b92107..4d22852e1f9f 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -631,6 +631,9 @@ FileLoaders + + Core + @@ -1176,6 +1179,9 @@ FileLoaders + + Core + diff --git a/Core/HLE/sceAtrac.cpp b/Core/HLE/sceAtrac.cpp index 3df69bbfe145..b6f05d8f39a3 100644 --- a/Core/HLE/sceAtrac.cpp +++ b/Core/HLE/sceAtrac.cpp @@ -176,7 +176,7 @@ struct Atrac { channels_(0), outputChannels_(2), bitrate_(64), bytesPerFrame_(0), bufferMaxSize_(0), jointStereo_(0), currentSample_(0), endSample_(0), firstSampleOffset_(0), dataOff_(0), loopStartSample_(-1), loopEndSample_(-1), loopNum_(0), - failedDecode_(false), codecType_(0), ignoreDataBuf_(false), + failedDecode_(false), ignoreDataBuf_(false), codecType_(0), bufferState_(ATRAC_STATUS_NO_DATA) { memset(&first_, 0, sizeof(first_)); memset(&second_, 0, sizeof(second_)); diff --git a/Core/HW/MediaEngine.cpp b/Core/HW/MediaEngine.cpp index da4140b88b75..a927a9c3bda9 100644 --- a/Core/HW/MediaEngine.cpp +++ b/Core/HW/MediaEngine.cpp @@ -39,8 +39,6 @@ extern "C" { } #endif // USE_FFMPEG -int g_iNumVideos = 0; - #ifdef USE_FFMPEG static AVPixelFormat getSwsFormat(int pspFormat) { @@ -149,12 +147,10 @@ MediaEngine::MediaEngine(): m_pdata(0) { m_ringbuffersize = 0; m_mpegheaderReadPos = 0; m_audioType = PSP_CODEC_AT3PLUS; // in movie, we use only AT3+ audio - g_iNumVideos++; } MediaEngine::~MediaEngine() { closeMedia(); - g_iNumVideos--; } void MediaEngine::closeMedia() { diff --git a/Core/HW/SasReverb.cpp b/Core/HW/SasReverb.cpp index c1d148342c7d..fd9f592b8be0 100644 --- a/Core/HW/SasReverb.cpp +++ b/Core/HW/SasReverb.cpp @@ -182,7 +182,7 @@ void SasReverb::SetPreset(int preset) { template class BufferWrapper { public: - BufferWrapper(int16_t *buffer, int position, int usedSize) : buf_(buffer), pos_(position), base_(bufsize - usedSize), end_(bufsize), size_(usedSize) {} + BufferWrapper(int16_t *buffer, int position, int usedSize) : buf_(buffer), pos_(position), end_(bufsize), base_(bufsize - usedSize), size_(usedSize) {} int16_t &operator [](int index) { int addr = pos_ + index; if (addr >= end_) { addr -= size_; } diff --git a/Core/System.cpp b/Core/System.cpp index d1613cc61742..5a78541596d7 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -586,6 +586,8 @@ std::string GetSysDirectory(PSPDirectories directoryType) { return g_Config.memStickDirectory + "PSP/PPSSPP_STATE/"; case DIRECTORY_CACHE: return g_Config.memStickDirectory + "PSP/SYSTEM/CACHE/"; + case DIRECTORY_TEXTURES: + return g_Config.memStickDirectory + "PSP/TEXTURES/"; case DIRECTORY_APP_CACHE: if (!g_Config.appCacheDirectory.empty()) { return g_Config.appCacheDirectory; diff --git a/Core/System.h b/Core/System.h index 4ff1767d8781..7815b60accc1 100644 --- a/Core/System.h +++ b/Core/System.h @@ -45,6 +45,7 @@ enum PSPDirectories { DIRECTORY_DUMP, DIRECTORY_SAVESTATE, DIRECTORY_CACHE, + DIRECTORY_TEXTURES, DIRECTORY_APP_CACHE, // Use the OS app cache if available }; diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp new file mode 100644 index 000000000000..4da568218e26 --- /dev/null +++ b/Core/TextureReplacer.cpp @@ -0,0 +1,470 @@ +// 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/. + +#ifndef USING_QT_UI +#include +#endif + +#include +#include "ext/xxhash.h" +#include "file/ini_file.h" +#include "Common/ColorConv.h" +#include "Common/FileUtil.h" +#include "Core/Config.h" +#include "Core/System.h" +#include "Core/TextureReplacer.h" +#include "Core/ELF/ParamSFO.h" +#include "GPU/Common/TextureDecoder.h" + +static const std::string INI_FILENAME = "textures.ini"; +static const std::string NEW_TEXTURE_DIR = "new/"; +static const int VERSION = 1; +static const int MAX_MIP_LEVELS = 64; + +TextureReplacer::TextureReplacer() : enabled_(false), allowVideo_(false), hash_(ReplacedTextureHash::QUICK) { + none_.alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; +} + +TextureReplacer::~TextureReplacer() { +} + +void TextureReplacer::Init() { + NotifyConfigChanged(); +} + +void TextureReplacer::NotifyConfigChanged() { + gameID_ = g_paramSFO.GetValueString("DISC_ID"); + + enabled_ = !gameID_.empty() && (g_Config.bReplaceTextures || g_Config.bSaveNewTextures); + if (enabled_) { + basePath_ = GetSysDirectory(DIRECTORY_TEXTURES) + gameID_ + "/"; + + // If we're saving, auto-create the directory. + if (g_Config.bSaveNewTextures && !File::Exists(basePath_ + NEW_TEXTURE_DIR)) { + File::CreateFullPath(basePath_ + NEW_TEXTURE_DIR); + } + + enabled_ = File::Exists(basePath_) && File::IsDirectory(basePath_); + } + + if (enabled_) { + enabled_ = LoadIni(); + } +} + +bool TextureReplacer::LoadIni() { + // TODO: Use crc32c? + hash_ = ReplacedTextureHash::QUICK; + aliases_.clear(); + hashranges_.clear(); + + if (File::Exists(basePath_ + INI_FILENAME)) { + IniFile ini; + ini.LoadFromVFS(basePath_ + INI_FILENAME); + + auto options = ini.GetOrCreateSection("options"); + std::string hash; + options->Get("hash", &hash, ""); + // TODO: crc32c. + if (strcasecmp(hash.c_str(), "quick") == 0) { + hash_ = ReplacedTextureHash::QUICK; + } else { + ERROR_LOG(G3D, "Unsupported hash type: %s", hash.c_str()); + return false; + } + + options->Get("video", &allowVideo_, false); + + int version = 0; + if (options->Get("version", &version, 0) && version > VERSION) { + ERROR_LOG(G3D, "Unsupported texture replacement version %d, trying anyway", version); + } + + std::vector hashNames; + if (ini.GetKeys("hashes", hashNames)) { + auto hashes = ini.GetOrCreateSection("hashes"); + // Format: hashname = filename.png + for (std::string hashName : hashNames) { + std::transform(hashName.begin(), hashName.end(), hashName.begin(), tolower); + hashes->Get(hashName.c_str(), &aliases_[hashName], ""); + } + } + + std::vector hashrangeKeys; + if (ini.GetKeys("hashranges", hashrangeKeys)) { + auto hashranges = ini.GetOrCreateSection("hashranges"); + // Format: addr,w,h = newW,newH + for (const std::string &key : hashrangeKeys) { + std::string value; + if (hashranges->Get(key.c_str(), &value, "")) { + ParseHashRange(key, value); + } + } + } + } + + // The ini doesn't have to exist for it to be valid. + return true; +} + +void TextureReplacer::ParseHashRange(const std::string &key, const std::string &value) { + std::vector keyParts; + SplitString(key, ',', keyParts); + std::vector valueParts; + SplitString(value, ',', valueParts); + + if (keyParts.size() != 3 || valueParts.size() != 2) { + ERROR_LOG(G3D, "Ignoring invalid hashrange %s = %s, expecting addr,w,h = w,h", key.c_str(), value.c_str()); + return; + } + + u32 addr; + u32 fromW; + u32 fromH; + if (!TryParse(keyParts[0], &addr) || !TryParse(keyParts[1], &fromW) || !TryParse(keyParts[2], &fromH)) { + ERROR_LOG(G3D, "Ignoring invalid hashrange %s = %s, key format is 0x12345678,512,512", key.c_str(), value.c_str()); + return; + } + + u32 toW; + u32 toH; + if (!TryParse(valueParts[0], &toW) || !TryParse(valueParts[1], &toH)) { + ERROR_LOG(G3D, "Ignoring invalid hashrange %s = %s, value format is 512,512", key.c_str(), value.c_str()); + return; + } + + if (toW > fromW || toH > fromH) { + ERROR_LOG(G3D, "Ignoring invalid hashrange %s = %s, range bigger than source", key.c_str(), value.c_str()); + return; + } + + const u64 rangeKey = ((u64)addr << 32) | (fromW << 16) | fromH; + hashranges_[rangeKey] = WidthHeightPair(toW, toH); +} + +u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV) { + _dbg_assert_msg_(G3D, enabled_, "Replacement not enabled"); + + if (!LookupHashRange(addr, w, h)) { + // There wasn't any hash range, let's fall back to maxSeenV logic. + if (h == 512 && maxSeenV < 512 && maxSeenV != 0) { + h = (int)maxSeenV; + } + } + + const u8 *checkp = Memory::GetPointer(addr); + if (bufw <= w) { + // We can assume the data is contiguous. These are the total used pixels. + const u32 totalPixels = bufw * h + (w - bufw); + const u32 sizeInRAM = (textureBitsPerPixel[fmt] * totalPixels) / 8; + + switch (hash_) { + case ReplacedTextureHash::QUICK: + return StableQuickTexHash(checkp, sizeInRAM); + default: + return 0; + } + } else { + // We have gaps. Let's hash each row and sum. + const u32 bytesPerLine = (textureBitsPerPixel[fmt] * w) / 8; + const u32 stride = (textureBitsPerPixel[fmt] * bufw) / 8; + + u32 result = 0; + switch (hash_) { + case ReplacedTextureHash::QUICK: + for (int y = 0; y < h; ++y) { + u32 rowHash = StableQuickTexHash(checkp, bytesPerLine); + result = (result * 11) ^ rowHash; + checkp += stride; + } + break; + + default: + break; + } + + return result; + } +} + +ReplacedTexture &TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, int h) { + // Only actually replace if we're replacing. We might just be saving. + if (!Enabled() || !g_Config.bReplaceTextures) { + return none_; + } + + ReplacementCacheKey replacementKey(cachekey, hash); + auto it = cache_.find(replacementKey); + if (it != cache_.end()) { + return it->second; + } + + // Okay, let's construct the result. + ReplacedTexture &result = cache_[replacementKey]; + result.alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; + PopulateReplacement(&result, cachekey, hash, w, h); + return result; +} + +void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h) { + int newW = w; + int newH = h; + LookupHashRange(cachekey >> 32, newW, newH); + + for (int i = 0; i < MAX_MIP_LEVELS; ++i) { + const std::string hashfile = LookupHashFile(cachekey, hash, i); + const std::string filename = basePath_ + hashfile; + if (hashfile.empty() || !File::Exists(filename)) { + // Out of valid mip levels. Bail out. + break; + } + + ReplacedTextureLevel level; + level.fmt = ReplacedTextureFormat::F_8888; + level.file = filename; + +#ifdef USING_QT_UI + ERROR_LOG(G3D, "Replacement texture loading not implemented for Qt"); +#else + png_image png = {}; + png.version = PNG_IMAGE_VERSION; + FILE *fp = File::OpenCFile(filename, "rb"); + if (png_image_begin_read_from_stdio(&png, fp)) { + // We pad files that have been hashrange'd so they are the same texture size. + level.w = (png.width * w) / newW; + level.h = (png.height * h) / newH; + + result->levels_.push_back(level); + } else { + ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s", filename.c_str(), png.message); + } + fclose(fp); + + png_image_free(&png); +#endif + } +} + +#ifndef USING_QT_UI +static bool WriteTextureToPNG(png_imagep image, const std::string &filename, int convert_to_8bit, const void *buffer, png_int_32 row_stride, const void *colormap) { + FILE *fp = File::OpenCFile(filename, "wb"); + if (!fp) { + ERROR_LOG(COMMON, "Unable to open texture file for writing."); + return false; + } + + if (png_image_write_to_stdio(image, fp, convert_to_8bit, buffer, row_stride, colormap)) { + if (fclose(fp) != 0) { + ERROR_LOG(COMMON, "Texture file write failed."); + return false; + } + return true; + } else { + ERROR_LOG(COMMON, "Texture PNG encode failed."); + fclose(fp); + remove(filename.c_str()); + return false; + } +} +#endif + +void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int w, int h) { + _assert_msg_(G3D, enabled_, "Replacement not enabled"); + if (!g_Config.bSaveNewTextures) { + // Ignore. + return; + } + if (replacedInfo.addr > 0x05000000 && replacedInfo.addr < 0x08800000) { + // Don't save the PPGe texture. + return; + } + if (replacedInfo.isVideo && !allowVideo_) { + return; + } + + std::string hashfile = LookupHashFile(replacedInfo.cachekey, replacedInfo.hash, level); + const std::string filename = basePath_ + hashfile; + const std::string saveFilename = basePath_ + NEW_TEXTURE_DIR + hashfile; + + // If it's empty, it's an ignored hash, we intentionally don't save. + if (hashfile.empty() || File::Exists(filename)) { + // If it exists, must've been decoded and saved as a new texture already. + return; + } + + ReplacementCacheKey replacementKey(replacedInfo.cachekey, replacedInfo.hash); + auto it = savedCache_.find(replacementKey); + if (it != savedCache_.end() && File::Exists(saveFilename)) { + // We've already saved this texture. Let's only save if it's bigger (e.g. scaled now.) + if (it->second.w >= w && it->second.h >= h) { + return; + } + } + + // Only save the hashed portion of the PNG. + int lookupW = w / replacedInfo.scaleFactor; + int lookupH = h / replacedInfo.scaleFactor; + if (LookupHashRange(replacedInfo.addr, lookupW, lookupH)) { + w = lookupW * replacedInfo.scaleFactor; + h = lookupH * replacedInfo.scaleFactor; + } + +#ifdef USING_QT_UI + ERROR_LOG(G3D, "Replacement texture saving not implemented for Qt"); +#else + if (replacedInfo.fmt != ReplacedTextureFormat::F_8888) { + saveBuf.resize((pitch * h) / sizeof(u16)); + switch (replacedInfo.fmt) { + case ReplacedTextureFormat::F_5650: + ConvertRGBA565ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); + break; + case ReplacedTextureFormat::F_5551: + ConvertRGBA5551ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); + break; + case ReplacedTextureFormat::F_4444: + ConvertRGBA4444ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); + break; + case ReplacedTextureFormat::F_0565_ABGR: + ConvertABGR565ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); + break; + case ReplacedTextureFormat::F_1555_ABGR: + ConvertABGR1555ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); + break; + case ReplacedTextureFormat::F_4444_ABGR: + ConvertABGR4444ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); + break; + case ReplacedTextureFormat::F_8888_BGRA: + ConvertBGRA8888ToRGBA8888(saveBuf.data(), (const u32 *)data, (pitch * h) / sizeof(u32)); + break; + } + + data = saveBuf.data(); + if (replacedInfo.fmt != ReplacedTextureFormat::F_8888_BGRA) { + // We doubled our pitch. + pitch *= 2; + } + } + + png_image png; + memset(&png, 0, sizeof(png)); + png.version = PNG_IMAGE_VERSION; + png.format = PNG_FORMAT_RGBA; + png.width = w; + png.height = h; + bool success = WriteTextureToPNG(&png, saveFilename, 0, data, pitch, nullptr); + png_image_free(&png); + + if (png.warning_or_error >= 2) { + ERROR_LOG(COMMON, "Saving screenshot to PNG produced errors."); + } else if (success) { + NOTICE_LOG(G3D, "Saving texture for replacement: %08x / %dx%d", replacedInfo.hash, w, h); + } +#endif + + // Remember that we've saved this for next time. + ReplacedTextureLevel saved; + saved.fmt = ReplacedTextureFormat::F_8888; + saved.file = filename; + saved.w = w; + saved.h = h; + savedCache_[replacementKey] = saved; +} + +std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, int level) { + const std::string hashname = HashName(cachekey, hash, level); + auto alias = aliases_.find(hashname); + if (alias != aliases_.end()) { + // Note: this will be blank if explicitly ignored. + return alias->second; + } + + // Also check for a cachekey-only alias. This is mainly for ignoring videos. + const std::string keyonly = hashname.substr(0, 16); + auto keyonlyAlias = aliases_.find(keyonly); + if (keyonlyAlias != aliases_.end()) { + // Note: this will be blank if explicitly ignored. + return keyonlyAlias->second; + } + + return hashname + ".png"; +} + +std::string TextureReplacer::HashName(u64 cachekey, u32 hash, int level) { + char hashname[16 + 8 + 1 + 11 + 1] = {}; + if (level > 0) { + snprintf(hashname, sizeof(hashname), "%016llx%08x_%d", cachekey, hash, level); + } else { + snprintf(hashname, sizeof(hashname), "%016llx%08x", cachekey, hash); + } + + return hashname; +} + +bool TextureReplacer::LookupHashRange(u32 addr, int &w, int &h) { + const u64 rangeKey = ((u64)addr << 32) | (w << 16) | h; + auto range = hashranges_.find(rangeKey); + if (range != hashranges_.end()) { + const WidthHeightPair &wh = range->second; + w = wh.first; + h = wh.second; + return true; + } + + return false; +} + +void ReplacedTexture::Load(int level, void *out, int rowPitch) { + _assert_msg_(G3D, (size_t)level < levels_.size(), "Invalid miplevel"); + _assert_msg_(G3D, out != nullptr && rowPitch > 0, "Invalid out/pitch"); + + const ReplacedTextureLevel &info = levels_[level]; + +#ifdef USING_QT_UI + ERROR_LOG(G3D, "Replacement texture loading not implemented for Qt"); +#else + png_image png = {}; + png.version = PNG_IMAGE_VERSION; + + FILE *fp = File::OpenCFile(info.file, "rb"); + if (!png_image_begin_read_from_stdio(&png, fp)) { + ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s", info.file.c_str(), png.message); + return; + } + + bool checkedAlpha = false; + if ((png.format & PNG_FORMAT_FLAG_ALPHA) == 0) { + // Well, we know for sure it doesn't have alpha. + alphaStatus_ = ReplacedTextureAlpha::FULL; + checkedAlpha = true; + } + png.format = PNG_FORMAT_RGBA; + + if (!png_image_finish_read(&png, nullptr, out, rowPitch, nullptr)) { + ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", info.file.c_str(), png.message); + return; + } + + if (level == 0 && !checkedAlpha) { + // This will only check the hashed bits. + CheckAlphaResult res = CheckAlphaRGBA8888Basic((u32 *)out, rowPitch / sizeof(u32), png.width, png.height); + alphaStatus_ = ReplacedTextureAlpha(res); + } + + fclose(fp); + png_image_free(&png); +#endif +} diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h new file mode 100644 index 000000000000..d90d484e28b5 --- /dev/null +++ b/Core/TextureReplacer.h @@ -0,0 +1,188 @@ +// 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 +#include +#include +#include +#include "Common/Common.h" +#include "Common/MemoryUtil.h" +#include "GPU/ge_constants.h" + +class TextureCacheCommon; +class TextureReplacer; + +enum class ReplacedTextureFormat { + F_5650, + F_5551, + F_4444, + F_8888, + F_0565_ABGR, + F_1555_ABGR, + F_4444_ABGR, + F_8888_BGRA, +}; + +// These must match the constants in TextureCacheCommon. +enum class ReplacedTextureAlpha { + UNKNOWN = 0x04, + FULL = 0x00, + SIMPLE = 0x08, +}; + +// For forward comatibility, we specify the hash. +enum class ReplacedTextureHash { + // TODO: Maybe only support crc32c for now? + QUICK, +}; + +struct ReplacedTextureLevel { + int w; + int h; + ReplacedTextureFormat fmt; + std::string file; +}; + +struct ReplacementCacheKey { + u64 cachekey; + u32 hash; + + ReplacementCacheKey(u64 c, u32 h) : cachekey(c), 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; + } +}; + +#ifndef __SYMBIAN32__ +namespace std { + template <> + struct hash { + size_t operator()(const ReplacementCacheKey &k) const { + return std::hash()(k.cachekey ^ ((u64)k.hash << 32)); + } + }; +} +#endif + +struct ReplacedTexture { + inline bool Valid() { + return !levels_.empty(); + } + + bool GetSize(int level, int &w, int &h) { + if ((size_t)level < levels_.size()) { + w = levels_[level].w; + h = levels_[level].h; + return true; + } + return false; + } + + int MaxLevel() { + return (int)levels_.size() - 1; + } + + ReplacedTextureFormat Format(int level) { + if ((size_t)level < levels_.size()) { + return levels_[level].fmt; + } + return ReplacedTextureFormat::F_8888; + } + + u8 AlphaStatus() { + return (u8)alphaStatus_; + } + + void Load(int level, void *out, int rowPitch); + +protected: + std::vector levels_; + ReplacedTextureAlpha alphaStatus_; + + friend TextureReplacer; +}; + +struct ReplacedTextureDecodeInfo { + u64 cachekey; + u32 hash; + u32 addr; + bool isVideo; + bool isFinal; + int scaleFactor; + ReplacedTextureFormat fmt; +}; + +class TextureReplacer { +public: + TextureReplacer(); + ~TextureReplacer(); + + void Init(); + void NotifyConfigChanged(); + + inline bool Enabled() { + return enabled_; + } + + u32 ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV); + + ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h); + + void NotifyTextureDecoded(const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int w, int h); + +protected: + bool LoadIni(); + void ParseHashRange(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); + void PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h); + + SimpleBuf saveBuf; + bool enabled_; + bool allowVideo_; + std::string gameID_; + std::string basePath_; + ReplacedTextureHash hash_; + std::unordered_map aliases_; + typedef std::pair WidthHeightPair; +#ifdef __SYMBIAN32__ + std::map hashranges_; +#else + std::unordered_map hashranges_; +#endif + + ReplacedTexture none_; +#ifdef __SYMBIAN32__ + std::map cache_; + std::map savedCache_; +#else + std::unordered_map cache_; + std::unordered_map savedCache_; +#endif +}; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 0c607e7a4353..4f423d4604ad 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -33,8 +33,8 @@ #include #endif -// Ugly. -extern int g_iNumVideos; +// Videos should be updated every few frames, so we forge quickly. +#define VIDEO_DECIMATE_AGE 4 TextureCacheCommon::TextureCacheCommon() : cacheSizeEstimate_(0), nextTexture_(nullptr), @@ -51,6 +51,8 @@ TextureCacheCommon::TextureCacheCommon() tmpTexBuf32.resize(512 * 512); // 1MB tmpTexBuf16.resize(512 * 512); // 0.5MB tmpTexBufRearrange.resize(512 * 512); // 1MB + + replacer.Init(); } TextureCacheCommon::~TextureCacheCommon() { @@ -74,7 +76,7 @@ int TextureCacheCommon::AttachedDrawingHeight() { return 0; } -void TextureCacheCommon::GetSamplingParams(int &minFilt, int &magFilt, bool &sClamp, bool &tClamp, float &lodBias, u8 maxLevel) { +void TextureCacheCommon::GetSamplingParams(int &minFilt, int &magFilt, bool &sClamp, bool &tClamp, float &lodBias, u8 maxLevel, u32 addr) { minFilt = gstate.texfilter & 0x7; magFilt = (gstate.texfilter >> 8) & 1; sClamp = gstate.isTexCoordClampedS(); @@ -91,9 +93,12 @@ void TextureCacheCommon::GetSamplingParams(int &minFilt, int &magFilt, bool &sCl lodBias = (float)(int)(s8)((gstate.texlevel >> 16) & 0xFF) / 16.0f; } - if (g_Config.iTexFiltering == TEX_FILTER_LINEAR_VIDEO && g_iNumVideos > 0 && (gstate.getTextureDimension(0) & 0xF) >= 9) { - magFilt |= 1; - minFilt |= 1; + if (g_Config.iTexFiltering == TEX_FILTER_LINEAR_VIDEO) { + bool isVideo = videos_.find(addr & 0x3FFFFFFF) != videos_.end(); + if (isVideo) { + magFilt |= 1; + minFilt |= 1; + } } if (g_Config.iTexFiltering == TEX_FILTER_LINEAR && (!gstate.isColorTestEnabled() || IsColorTestTriviallyTrue())) { if (!gstate.isAlphaTestEnabled() || IsAlphaTestTriviallyTrue()) { @@ -143,6 +148,18 @@ void TextureCacheCommon::UpdateMaxSeenV(bool throughMode) { } } +void TextureCacheCommon::DecimateVideos() { + if (!videos_.empty()) { + for (auto iter = videos_.begin(); iter != videos_.end(); ) { + if (iter->second + VIDEO_DECIMATE_AGE < gpuStats.numFlips) { + videos_.erase(iter++); + } else { + ++iter; + } + } + } +} + void TextureCacheCommon::NotifyFramebuffer(u32 address, VirtualFramebuffer *framebuffer, FramebufferNotification msg) { // Must be in VRAM so | 0x04000000 it is. Also, ignore memory mirrors. // These checks are mainly to reduce scanning all textures. @@ -287,6 +304,13 @@ void TextureCacheCommon::NotifyConfigChanged() { } standardScaleFactor_ = scaleFactor; + + replacer.NotifyConfigChanged(); +} + +void TextureCacheCommon::NotifyVideoUpload(u32 addr, int size, int width, GEBufferFormat fmt) { + addr &= 0x3FFFFFFF; + videos_[addr] = gpuStats.numFlips; } void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index d2af36993eb2..bf8e11e7d6b6 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -21,8 +21,9 @@ #include #include "Common/CommonTypes.h" -#include "GPU/Common/GPUDebugInterface.h" #include "Common/MemoryUtil.h" +#include "Core/TextureReplacer.h" +#include "GPU/Common/GPUDebugInterface.h" enum TextureFiltering { TEX_FILTER_AUTO = 1, @@ -54,6 +55,7 @@ class TextureCacheCommon { // FramebufferManager keeps TextureCache updated about what regions of memory are being rendered to. void NotifyFramebuffer(u32 address, VirtualFramebuffer *framebuffer, FramebufferNotification msg); void NotifyConfigChanged(); + void NotifyVideoUpload(u32 addr, int size, int width, GEBufferFormat fmt); int AttachedDrawingHeight(); @@ -147,13 +149,17 @@ class TextureCacheCommon { void *RearrangeBuf(void *inBuf, u32 inRowBytes, u32 outRowBytes, int h, bool allowInPlace = true); u32 EstimateTexMemoryUsage(const TexCacheEntry *entry); - void GetSamplingParams(int &minFilt, int &magFilt, bool &sClamp, bool &tClamp, float &lodBias, u8 maxLevel); + void GetSamplingParams(int &minFilt, int &magFilt, bool &sClamp, bool &tClamp, float &lodBias, u8 maxLevel, u32 addr); void UpdateMaxSeenV(bool throughMode); virtual bool AttachFramebuffer(TexCacheEntry *entry, u32 address, VirtualFramebuffer *framebuffer, u32 texaddrOffset = 0) = 0; virtual void DownloadFramebufferForClut(u32 clutAddr, u32 bytes) = 0; + void DecimateVideos(); + + TextureReplacer replacer; + TexCache cache; u32 cacheSizeEstimate_; @@ -168,6 +174,8 @@ class TextureCacheCommon { void AttachFramebufferInvalid(TexCacheEntry *entry, VirtualFramebuffer *framebuffer, const AttachedFramebufferInfo &fbInfo); void DetachFramebuffer(TexCacheEntry *entry, u32 address, VirtualFramebuffer *framebuffer); + std::map videos_; + SimpleBuf tmpTexBuf32; SimpleBuf tmpTexBuf16; SimpleBuf tmpTexBufRearrange; diff --git a/GPU/Common/TextureDecoder.cpp b/GPU/Common/TextureDecoder.cpp index 961dca9f3623..bb0afee365be 100644 --- a/GPU/Common/TextureDecoder.cpp +++ b/GPU/Common/TextureDecoder.cpp @@ -301,6 +301,7 @@ void DoUnswizzleTex16Basic(const u8 *texptr, u32 *ydestp, int bxc, int byc, u32 #ifndef _M_SSE #ifndef ARM64 QuickTexHashFunc DoQuickTexHash = &QuickTexHashBasic; +QuickTexHashFunc StableQuickTexHash = &QuickTexHashNonSSE; UnswizzleTex16Func DoUnswizzleTex16 = &DoUnswizzleTex16Basic; ReliableHash32Func DoReliableHash32 = &XXH32; ReliableHash64Func DoReliableHash64 = &XXH64; @@ -312,6 +313,7 @@ void SetupTextureDecoder() { #ifdef HAVE_ARMV7 if (cpu_info.bNEON) { DoQuickTexHash = &QuickTexHashNEON; + StableQuickTexHash = &QuickTexHashNEON; DoUnswizzleTex16 = &DoUnswizzleTex16NEON; #ifndef IOS // Not sure if this is safe on iOS, it's had issues with xxhash. diff --git a/GPU/Common/TextureDecoder.h b/GPU/Common/TextureDecoder.h index d3ddca94758c..6b20498e1ba6 100644 --- a/GPU/Common/TextureDecoder.h +++ b/GPU/Common/TextureDecoder.h @@ -39,6 +39,7 @@ void DoSwizzleTex16(const u32 *ysrcp, u8 *texptr, int bxc, int byc, u32 pitch); #if defined(_M_SSE) u32 QuickTexHashSSE2(const void *checkp, u32 size); #define DoQuickTexHash QuickTexHashSSE2 +#define StableQuickTexHash QuickTexHashSSE2 // Pitch must be aligned to 16 bits (as is the case on a PSP) void DoUnswizzleTex16Basic(const u8 *texptr, u32 *ydestp, int bxc, int byc, u32 pitch); @@ -59,6 +60,7 @@ typedef u32 ReliableHashType; // For ARM64, NEON is mandatory, so we also statically link. #elif defined(ARM64) #define DoQuickTexHash QuickTexHashNEON +#define StableQuickTexHash QuickTexHashNEON #define DoUnswizzleTex16 DoUnswizzleTex16NEON #define DoReliableHash32 ReliableHash32NEON @@ -71,6 +73,7 @@ typedef u64 ReliableHashType; #else typedef u32 (*QuickTexHashFunc)(const void *checkp, u32 size); extern QuickTexHashFunc DoQuickTexHash; +extern QuickTexHashFunc StableQuickTexHash; typedef void (*UnswizzleTex16Func)(const u8 *texptr, u32 *ydestp, int bxc, int byc, u32 pitch); extern UnswizzleTex16Func DoUnswizzleTex16; diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 0d58cde4ad8f..075626d140d7 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -152,7 +152,7 @@ void PrintDecodedVertex(VertexReader &vtx) { printf("P: %f %f %f\n", pos[0], pos[1], pos[2]); } -VertexDecoder::VertexDecoder() : jitted_(0), jittedSize_(0), decoded_(nullptr), ptr_(nullptr) { +VertexDecoder::VertexDecoder() : decoded_(nullptr), ptr_(nullptr), jitted_(0), jittedSize_(0) { } void VertexDecoder::Step_WeightsU8() const diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 190c99d6d992..9e29ad3dd8db 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -2006,6 +2006,7 @@ void GPU_DX9::NotifyVideoUpload(u32 addr, int size, int width, int format) { if (Memory::IsVRAMAddress(addr)) { framebufferManager_.NotifyVideoUpload(addr, size, width, (GEBufferFormat)format); } + textureCache_.NotifyVideoUpload(addr, size, width, (GEBufferFormat)format); InvalidateCache(addr, size, GPU_INVALIDATE_SAFE); } diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 37c6e833fa83..dae0ff03b848 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -38,8 +38,6 @@ #include "math/math_util.h" -extern int g_iNumVideos; - namespace DX9 { #define INVALID_TEX (LPDIRECT3DTEXTURE9)(-1) @@ -112,6 +110,7 @@ void TextureCacheDX9::Clear(bool delete_them) { secondCacheSizeEstimate_ = 0; } fbTexInfo_.clear(); + videos_.clear(); } void TextureCacheDX9::DeleteTexture(TexCache::iterator it) { @@ -170,6 +169,8 @@ void TextureCacheDX9::Decimate() { VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_); } + + DecimateVideos(); } void TextureCacheDX9::Invalidate(u32 addr, int size, GPUInvalidationType type) { @@ -500,7 +501,7 @@ void TextureCacheDX9::UpdateSamplingParams(TexCacheEntry &entry, bool force) { bool sClamp; bool tClamp; float lodBias; - GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, entry.maxLevel); + GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, entry.maxLevel, entry.addr); if (entry.maxLevel != 0) { GETexLevelMode mode = gstate.getTexLevelMode(); @@ -540,7 +541,7 @@ void TextureCacheDX9::SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHe bool sClamp; bool tClamp; float lodBias; - GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, 0); + GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, 0, 0); dxstate.texMinFilter.set(MinFilt[minFilt]); dxstate.texMipFilter.set(MipFilt[minFilt]); @@ -585,7 +586,11 @@ static inline u32 MiniHash(const u32 *ptr) { return ptr[0]; } -static inline u32 QuickTexHash(u32 addr, int bufw, int w, int h, GETextureFormat format, TextureCacheDX9::TexCacheEntry *entry) { +static inline u32 QuickTexHash(TextureReplacer &replacer, u32 addr, int bufw, int w, int h, GETextureFormat format, TextureCacheDX9::TexCacheEntry *entry) { + if (replacer.Enabled()) { + return replacer.ComputeHash(addr, bufw, w, h, format, entry->maxSeenV); + } + if (h == 512 && entry->maxSeenV < 512 && entry->maxSeenV != 0) { h = (int)entry->maxSeenV; } @@ -1035,13 +1040,13 @@ void TextureCacheDX9::SetTexture(bool force) { bool hashFail = false; if (texhash != entry->hash) { - fullhash = QuickTexHash(texaddr, bufw, w, h, format, entry); + fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); hashFail = true; rehash = false; } if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) { - fullhash = QuickTexHash(texaddr, bufw, w, h, format, entry); + fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); if (fullhash != entry->fullhash) { hashFail = true; } else { @@ -1188,7 +1193,7 @@ void TextureCacheDX9::SetTexture(bool force) { // to avoid excessive clearing caused by cache invalidations. entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; - entry->fullhash = fullhash == 0 ? QuickTexHash(texaddr, bufw, w, h, format, entry) : fullhash; + entry->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash; entry->cluthash = cluthash; entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; @@ -1251,6 +1256,16 @@ void TextureCacheDX9::SetTexture(bool force) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } + ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h); + if (replaced.GetSize(0, w, h)) { + // We're replacing, so we won't scale. + scaleFactor = 1; + if (g_Config.bMipMap) { + maxLevel = replaced.MaxLevel(); + badMipSizes = false; + } + } + // Don't scale the PPGe texture. if (entry->addr > 0x05000000 && entry->addr < 0x08800000) scaleFactor = 1; @@ -1279,7 +1294,7 @@ void TextureCacheDX9::SetTexture(bool force) { maxLevel = 0; } - LoadTextureLevel(*entry, 0, maxLevel, replaceImages, scaleFactor, dstFmt); + LoadTextureLevel(*entry, replaced, 0, maxLevel, replaceImages, scaleFactor, dstFmt); LPDIRECT3DTEXTURE9 &texture = DxTex(entry); if (!texture) { return; @@ -1288,10 +1303,14 @@ void TextureCacheDX9::SetTexture(bool force) { // Mipmapping is only enabled when texture scaling is disabled. if (maxLevel > 0 && scaleFactor == 1) { for (u32 i = 1; i <= maxLevel; i++) { - LoadTextureLevel(*entry, i, maxLevel, replaceImages, scaleFactor, dstFmt); + LoadTextureLevel(*entry, replaced, i, maxLevel, replaceImages, scaleFactor, dstFmt); } } + if (replaced.Valid()) { + entry->SetAlphaStatus(TexCacheEntry::Status(replaced.AlphaStatus())); + } + gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; gstate_c.textureSimpleAlpha = entry->GetAlphaStatus() != TexCacheEntry::STATUS_ALPHA_UNKNOWN; @@ -1590,31 +1609,83 @@ static inline void copyTexture(int xoffset, int yoffset, int w, int h, int pitch } } -void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, int level, int maxLevel, bool replaceImages, int scaleFactor, u32 dstFmt) { - // TODO: only do this once - u32 texByteAlign = 1; +ReplacedTextureFormat FromD3D9Format(u32 fmt) { + switch (fmt) { + case D3DFMT_R5G6B5: + return ReplacedTextureFormat::F_5650; + case D3DFMT_A1R5G5B5: + return ReplacedTextureFormat::F_5551; + case D3DFMT_A4R4G4B4: + return ReplacedTextureFormat::F_4444; + case D3DFMT_A8R8G8B8: + default: + return ReplacedTextureFormat::F_8888; + } +} - GEPaletteFormat clutformat = gstate.getClutPaletteFormat(); - int bufw; - void *finalBuf = DecodeTextureLevel(GETextureFormat(entry.format), clutformat, level, texByteAlign, dstFmt, &bufw); - if (finalBuf == NULL) { - return; +u32 ToD3D9Format(ReplacedTextureFormat fmt) { + switch (fmt) { + case ReplacedTextureFormat::F_5650: + return D3DFMT_R5G6B5; + case ReplacedTextureFormat::F_5551: + return D3DFMT_A1R5G5B5; + case ReplacedTextureFormat::F_4444: + return D3DFMT_A4R4G4B4; + case ReplacedTextureFormat::F_8888: + default: + return D3DFMT_A8R8G8B8; } +} + +void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int maxLevel, bool replaceImages, int scaleFactor, u32 dstFmt) { + // TODO: only do this once + u32 texByteAlign = 1; int w = gstate.getTextureWidth(level); int h = gstate.getTextureHeight(level); + int bufw; + u32 *pixelData; gpuStats.numTexturesDecoded++; + if (replaced.GetSize(level, w, h)) { + tmpTexBufRearrange.resize(w * h); + int bpp = replaced.Format(level) == ReplacedTextureFormat::F_8888 ? 4 : 2; + bufw = w; + replaced.Load(level, tmpTexBufRearrange.data(), bpp * w); + pixelData = tmpTexBufRearrange.data(); + + dstFmt = ToD3D9Format(replaced.Format(level)); + } else { + GEPaletteFormat clutformat = gstate.getClutPaletteFormat(); + void *finalBuf = DecodeTextureLevel(GETextureFormat(entry.format), clutformat, level, texByteAlign, dstFmt, &bufw); + if (finalBuf == NULL) { + return; + } - u32 *pixelData = (u32 *)finalBuf; - if (scaleFactor > 1 && (entry.status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) - scaler.Scale(pixelData, dstFmt, w, h, scaleFactor); + pixelData = (u32 *)finalBuf; + if (scaleFactor > 1 && (entry.status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) + scaler.Scale(pixelData, dstFmt, w, h, scaleFactor); - if ((entry.status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { - TexCacheEntry::Status alphaStatus = CheckAlpha(pixelData, dstFmt, w, w, h); - entry.SetAlphaStatus(alphaStatus, level); - } else { - entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); + if ((entry.status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { + TexCacheEntry::Status alphaStatus = CheckAlpha(pixelData, dstFmt, w, w, h); + entry.SetAlphaStatus(alphaStatus, level); + } else { + entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); + } + + if (replacer.Enabled()) { + ReplacedTextureDecodeInfo replacedInfo; + replacedInfo.cachekey = entry.CacheKey(); + replacedInfo.hash = entry.fullhash; + replacedInfo.addr = entry.addr; + replacedInfo.isVideo = videos_.find(entry.addr & 0x3FFFFFFF) != videos_.end(); + replacedInfo.isFinal = (entry.status & TexCacheEntry::STATUS_TO_SCALE) == 0; + replacedInfo.scaleFactor = scaleFactor; + replacedInfo.fmt = FromD3D9Format(dstFmt); + + int bpp = dstFmt == D3DFMT_A8R8G8B8 ? 4 : 2; + replacer.NotifyTextureDecoded(replacedInfo, pixelData, w * bpp, level, w, h); + } } LPDIRECT3DTEXTURE9 &texture = DxTex(&entry); diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index fea05abbdad1..ff9db0fa2077 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -80,7 +80,7 @@ class TextureCacheDX9 : public TextureCacheCommon { void DeleteTexture(TexCache::iterator it); void *ReadIndexedTex(int level, const u8 *texptr, int bytesPerIndex, u32 dstFmt, int bufw); void UpdateSamplingParams(TexCacheEntry &entry, bool force); - void LoadTextureLevel(TexCacheEntry &entry, int level, int maxLevel, bool replaceImages, int scaleFactor, u32 dstFmt); + void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int maxLevel, bool replaceImages, int scaleFactor, u32 dstFmt); D3DFORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; void *DecodeTextureLevel(GETextureFormat format, GEPaletteFormat clutformat, int level, u32 &texByteAlign, u32 &dstFmt, int *bufw = 0); TexCacheEntry::Status CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h); diff --git a/GPU/GLES/Framebuffer.cpp b/GPU/GLES/Framebuffer.cpp index b7d10993c0a4..8ecce9e131ef 100644 --- a/GPU/GLES/Framebuffer.cpp +++ b/GPU/GLES/Framebuffer.cpp @@ -50,8 +50,6 @@ // #define DEBUG_READ_PIXELS 1 -extern int g_iNumVideos; - static const char tex_fs[] = #ifdef USING_GLES2 "precision mediump float;\n" diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 3324f8f815ec..b542631a0a12 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -2261,6 +2261,7 @@ void GPU_GLES::NotifyVideoUpload(u32 addr, int size, int width, int format) { if (Memory::IsVRAMAddress(addr)) { framebufferManager_.NotifyVideoUpload(addr, size, width, (GEBufferFormat)format); } + textureCache_.NotifyVideoUpload(addr, size, width, (GEBufferFormat)format); InvalidateCache(addr, size, GPU_INVALIDATE_SAFE); } diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 50bee2cd62de..09bc84957dbc 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -71,9 +71,6 @@ #define INVALID_TEX -1 -// Hack! -extern int g_iNumVideos; - TextureCache::TextureCache() : secondCacheSizeEstimate_(0), clearCacheNextFrame_(false), lowMemoryMode_(false), clutBuf_(NULL), texelsScaledThisFrame_(0) { timesInvalidatedAllThisFrame_ = 0; lastBoundTexture = INVALID_TEX; @@ -114,6 +111,7 @@ void TextureCache::Clear(bool delete_them) { secondCacheSizeEstimate_ = 0; } fbTexInfo_.clear(); + videos_.clear(); } void TextureCache::DeleteTexture(TexCache::iterator it) { @@ -168,6 +166,8 @@ void TextureCache::Decimate() { VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_); } + + DecimateVideos(); } void TextureCache::Invalidate(u32 addr, int size, GPUInvalidationType type) { @@ -487,7 +487,7 @@ void TextureCache::UpdateSamplingParams(TexCacheEntry &entry, bool force) { bool sClamp; bool tClamp; float lodBias; - GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, entry.maxLevel); + GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, entry.maxLevel, entry.addr); if (entry.maxLevel != 0) { if (force || entry.lodBias != lodBias) { @@ -541,7 +541,7 @@ void TextureCache::SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeigh bool sClamp; bool tClamp; float lodBias; - GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, 0); + GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, 0, 0); minFilt &= 1; // framebuffers can't mipmap. @@ -606,7 +606,11 @@ static inline u32 MiniHash(const u32 *ptr) { return ptr[0]; } -static inline u32 QuickTexHash(u32 addr, int bufw, int w, int h, GETextureFormat format, TextureCache::TexCacheEntry *entry) { +static inline u32 QuickTexHash(TextureReplacer &replacer, u32 addr, int bufw, int w, int h, GETextureFormat format, TextureCache::TexCacheEntry *entry) { + if (replacer.Enabled()) { + return replacer.ComputeHash(addr, bufw, w, h, format, entry->maxSeenV); + } + if (h == 512 && entry->maxSeenV < 512 && entry->maxSeenV != 0) { h = (int)entry->maxSeenV; } @@ -989,6 +993,35 @@ bool TextureCache::SetOffsetTexture(u32 offset) { return false; } +ReplacedTextureFormat FromGLESFormat(GLenum fmt, bool useBGRA = false) { + // TODO: 16-bit formats are incorrect, since swizzled. + switch (fmt) { + case GL_UNSIGNED_SHORT_5_6_5: + return ReplacedTextureFormat::F_0565_ABGR; + case GL_UNSIGNED_SHORT_5_5_5_1: + return ReplacedTextureFormat::F_1555_ABGR; + case GL_UNSIGNED_SHORT_4_4_4_4: + return ReplacedTextureFormat::F_4444_ABGR; + case GL_UNSIGNED_BYTE: + default: + return useBGRA ? ReplacedTextureFormat::F_8888_BGRA : ReplacedTextureFormat::F_8888; + } +} + +GLenum ToGLESFormat(ReplacedTextureFormat fmt) { + switch (fmt) { + case ReplacedTextureFormat::F_5650: + return GL_UNSIGNED_SHORT_5_6_5; + case ReplacedTextureFormat::F_5551: + return GL_UNSIGNED_SHORT_5_5_5_1; + case ReplacedTextureFormat::F_4444: + return GL_UNSIGNED_SHORT_4_4_4_4; + case ReplacedTextureFormat::F_8888: + default: + return GL_UNSIGNED_BYTE; + } +} + void TextureCache::SetTexture(bool force) { #ifdef DEBUG_TEXTURES if (SetDebugTexture()) { @@ -1111,13 +1144,13 @@ void TextureCache::SetTexture(bool force) { bool hashFail = false; if (texhash != entry->hash) { - fullhash = QuickTexHash(texaddr, bufw, w, h, format, entry); + fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); hashFail = true; rehash = false; } if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) { - fullhash = QuickTexHash(texaddr, bufw, w, h, format, entry); + fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); if (fullhash != entry->fullhash) { hashFail = true; } else { @@ -1264,7 +1297,7 @@ void TextureCache::SetTexture(bool force) { // to avoid excessive clearing caused by cache invalidations. entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; - entry->fullhash = fullhash == 0 ? QuickTexHash(texaddr, bufw, w, h, format, entry) : fullhash; + entry->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash; entry->cluthash = cluthash; entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; @@ -1331,6 +1364,16 @@ void TextureCache::SetTexture(bool force) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } + ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h); + if (replaced.GetSize(0, w, h)) { + // We're replacing, so we won't scale. + scaleFactor = 1; + if (g_Config.bMipMap) { + maxLevel = replaced.MaxLevel(); + badMipSizes = false; + } + } + // Don't scale the PPGe texture. if (entry->addr > 0x05000000 && entry->addr < 0x08800000) scaleFactor = 1; @@ -1355,14 +1398,15 @@ void TextureCache::SetTexture(bool force) { // glTexStorage2D probably has few benefits for us anyway. if (false && gl_extensions.GLES3 && maxLevel > 0) { // glTexStorage2D requires the use of sized formats. + GLenum actualFmt = replaced.Valid() ? ToGLESFormat(replaced.Format(0)) : dstFmt; GLenum storageFmt = GL_RGBA8; - switch (dstFmt) { + switch (actualFmt) { case GL_UNSIGNED_BYTE: storageFmt = GL_RGBA8; break; case GL_UNSIGNED_SHORT_5_6_5: storageFmt = GL_RGB565; break; case GL_UNSIGNED_SHORT_4_4_4_4: storageFmt = GL_RGBA4; break; case GL_UNSIGNED_SHORT_5_5_5_1: storageFmt = GL_RGB5_A1; break; default: - ERROR_LOG(G3D, "Unknown dstfmt %i", (int)dstFmt); + ERROR_LOG(G3D, "Unknown dstfmt %i", (int)actualFmt); break; } // TODO: This may cause bugs, since it hard-sets the texture w/h, and we might try to reuse it later with a different size. @@ -1377,7 +1421,7 @@ void TextureCache::SetTexture(bool force) { // be as good quality as the game's own (might even be better in some cases though). // Always load base level texture here - LoadTextureLevel(*entry, 0, replaceImages, scaleFactor, dstFmt); + LoadTextureLevel(*entry, replaced, 0, replaceImages, scaleFactor, dstFmt); // Mipmapping only enable when texture scaling disable if (maxLevel > 0 && scaleFactor == 1) { @@ -1387,7 +1431,7 @@ void TextureCache::SetTexture(bool force) { glGenerateMipmap(GL_TEXTURE_2D); } else { for (int i = 1; i <= maxLevel; i++) { - LoadTextureLevel(*entry, i, replaceImages, scaleFactor, dstFmt); + LoadTextureLevel(*entry, replaced, i, replaceImages, scaleFactor, dstFmt); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, maxLevel); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, (float)maxLevel); @@ -1405,6 +1449,10 @@ void TextureCache::SetTexture(bool force) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); } + if (replaced.Valid()) { + entry->SetAlphaStatus(TexCacheEntry::Status(replaced.AlphaStatus())); + } + if (gstate_c.Supports(GPU_SUPPORTS_ANISOTROPY)) { int aniso = 1 << g_Config.iAnisotropyLevel; float anisotropyLevel = (float) aniso > maxAnisotropyLevel ? maxAnisotropyLevel : (float) aniso; @@ -1709,50 +1757,77 @@ TextureCache::TexCacheEntry::Status TextureCache::CheckAlpha(const u32 *pixelDat return (TexCacheEntry::Status)res; } -void TextureCache::LoadTextureLevel(TexCacheEntry &entry, int level, bool replaceImages, int scaleFactor, GLenum dstFmt) { +void TextureCache::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, bool replaceImages, int scaleFactor, GLenum dstFmt) { int w = gstate.getTextureWidth(level); int h = gstate.getTextureHeight(level); bool useUnpack = false; bool useBGRA; u32 *pixelData; - { - - PROFILE_THIS_SCOPE("decodetex"); // TODO: only do this once u32 texByteAlign = 1; - GEPaletteFormat clutformat = gstate.getClutPaletteFormat(); - int bufw; - void *finalBuf = DecodeTextureLevel(GETextureFormat(entry.format), clutformat, level, texByteAlign, dstFmt, scaleFactor, &bufw); - if (finalBuf == NULL) { - return; - } - gpuStats.numTexturesDecoded++; - // Can restore these and remove the fixup at the end of DecodeTextureLevel on desktop GL and GLES 3. - if (scaleFactor == 1 && gstate_c.Supports(GPU_SUPPORTS_UNPACK_SUBIMAGE) && w != bufw) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, bufw); - useUnpack = true; - } - - glPixelStorei(GL_UNPACK_ALIGNMENT, texByteAlign); + if (replaced.GetSize(level, w, h)) { + PROFILE_THIS_SCOPE("replacetex"); - useBGRA = UseBGRA8888() && dstFmt == GL_UNSIGNED_BYTE; + tmpTexBufRearrange.resize(w * h); + int bpp = replaced.Format(level) == ReplacedTextureFormat::F_8888 ? 4 : 2; + replaced.Load(level, tmpTexBufRearrange.data(), bpp * w); + pixelData = tmpTexBufRearrange.data(); - pixelData = (u32 *)finalBuf; - if (scaleFactor > 1) - scaler.Scale(pixelData, dstFmt, w, h, scaleFactor); + dstFmt = ToGLESFormat(replaced.Format(level)); - if ((entry.status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { - TexCacheEntry::Status alphaStatus = CheckAlpha(pixelData, dstFmt, useUnpack ? bufw : w, w, h); - entry.SetAlphaStatus(alphaStatus, level); + texByteAlign = bpp; + useBGRA = false; } else { - entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); - } + + PROFILE_THIS_SCOPE("decodetex"); + + GEPaletteFormat clutformat = gstate.getClutPaletteFormat(); + int bufw; + void *finalBuf = DecodeTextureLevel(GETextureFormat(entry.format), clutformat, level, texByteAlign, dstFmt, scaleFactor, &bufw); + if (finalBuf == NULL) { + return; + } + + // Can restore these and remove the fixup at the end of DecodeTextureLevel on desktop GL and GLES 3. + if (scaleFactor == 1 && gstate_c.Supports(GPU_SUPPORTS_UNPACK_SUBIMAGE) && w != bufw) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, bufw); + useUnpack = true; + } + + useBGRA = UseBGRA8888() && dstFmt == GL_UNSIGNED_BYTE; + + pixelData = (u32 *)finalBuf; + if (scaleFactor > 1) + scaler.Scale(pixelData, dstFmt, w, h, scaleFactor); + + if ((entry.status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { + TexCacheEntry::Status alphaStatus = CheckAlpha(pixelData, dstFmt, useUnpack ? bufw : w, w, h); + entry.SetAlphaStatus(alphaStatus, level); + } else { + entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); + } + + if (replacer.Enabled()) { + ReplacedTextureDecodeInfo replacedInfo; + replacedInfo.cachekey = entry.CacheKey(); + replacedInfo.hash = entry.fullhash; + replacedInfo.addr = entry.addr; + replacedInfo.isVideo = videos_.find(entry.addr & 0x3FFFFFFF) != videos_.end(); + replacedInfo.isFinal = (entry.status & TexCacheEntry::STATUS_TO_SCALE) == 0; + replacedInfo.scaleFactor = scaleFactor; + replacedInfo.fmt = FromGLESFormat(dstFmt, useBGRA); + + int bpp = dstFmt == GL_UNSIGNED_BYTE ? 4 : 2; + replacer.NotifyTextureDecoded(replacedInfo, pixelData, (useUnpack ? bufw : w) * bpp, level, w, h); + } } + glPixelStorei(GL_UNPACK_ALIGNMENT, texByteAlign); + GLuint components = dstFmt == GL_UNSIGNED_SHORT_5_6_5 ? GL_RGB : GL_RGBA; GLuint components2 = components; diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index 946ce0539446..10cddc6dcb7b 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -95,7 +95,7 @@ class TextureCache : public TextureCacheCommon { void DeleteTexture(TexCache::iterator it); void *ReadIndexedTex(int level, const u8 *texptr, int bytesPerIndex, GLuint dstFmt, int bufw); void UpdateSamplingParams(TexCacheEntry &entry, bool force); - void LoadTextureLevel(TexCacheEntry &entry, int level, bool replaceImages, int scaleFactor, GLenum dstFmt); + void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, bool replaceImages, int scaleFactor, GLenum dstFmt); GLenum GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; void *DecodeTextureLevel(GETextureFormat format, GEPaletteFormat clutformat, int level, u32 &texByteAlign, GLenum dstFmt, int scaleFactor, int *bufw = 0); TexCacheEntry::Status CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h); diff --git a/GPU/Vulkan/FramebufferVulkan.cpp b/GPU/Vulkan/FramebufferVulkan.cpp index 64ab3865ad2c..5fefbc7c3ea2 100644 --- a/GPU/Vulkan/FramebufferVulkan.cpp +++ b/GPU/Vulkan/FramebufferVulkan.cpp @@ -52,8 +52,6 @@ #include "UI/OnScreenDisplay.h" -extern int g_iNumVideos; - const VkFormat framebufFormat = VK_FORMAT_R8G8B8A8_UNORM; static const char tex_fs[] = R"(#version 400 diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 78111a790aea..6f91e2ec7c22 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -2167,7 +2167,12 @@ bool GPU_Vulkan::PerformMemorySet(u32 dest, u8 v, int size) { } void GPU_Vulkan::NotifyVideoUpload(u32 addr, int size, int width, int format) { - + if (Memory::IsVRAMAddress(addr)) { + // TODO + //framebufferManager_.NotifyVideoUpload(addr, size, width, (GEBufferFormat)format); + } + textureCache_.NotifyVideoUpload(addr, size, width, (GEBufferFormat)format); + InvalidateCache(addr, size, GPU_INVALIDATE_SAFE); } bool GPU_Vulkan::PerformMemoryDownload(u32 dest, int size) { diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 74c42fec1d2b..ca20481862bc 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -83,9 +83,6 @@ static const VkComponentMapping VULKAN_1555_SWIZZLE = { VK_COMPONENT_SWIZZLE_B, static const VkComponentMapping VULKAN_565_SWIZZLE = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; static const VkComponentMapping VULKAN_8888_SWIZZLE = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; -// Hack! -extern int g_iNumVideos; - SamplerCache::~SamplerCache() { for (auto iter : cache_) { vulkan_->Delete().QueueDeleteSampler(iter.second); @@ -177,6 +174,7 @@ void TextureCacheVulkan::Clear(bool delete_them) { secondCacheSizeEstimate_ = 0; } fbTexInfo_.clear(); + videos_.clear(); } void TextureCacheVulkan::DeleteTexture(TexCache::iterator it) { @@ -230,6 +228,8 @@ void TextureCacheVulkan::Decimate() { VERBOSE_LOG(G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_); } + + DecimateVideos(); } void TextureCacheVulkan::Invalidate(u32 addr, int size, GPUInvalidationType type) { @@ -505,7 +505,7 @@ void TextureCacheVulkan::UpdateSamplingParams(TexCacheEntry &entry, SamplerCache bool sClamp; bool tClamp; float lodBias; - GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, entry.maxLevel); + GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, entry.maxLevel, entry.addr); key.minFilt = minFilt & 1; key.mipEnable = (minFilt >> 2) & 1; key.mipFilt = (minFilt >> 1) & 1; @@ -546,7 +546,7 @@ void TextureCacheVulkan::SetFramebufferSamplingParams(u16 bufferWidth, u16 buffe bool sClamp; bool tClamp; float lodBias; - GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, 0); + GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, 0, 0); key.minFilt = minFilt & 1; key.mipFilt = 0; @@ -591,7 +591,11 @@ static inline u32 MiniHash(const u32 *ptr) { return ptr[0]; } -static inline u32 QuickTexHash(u32 addr, int bufw, int w, int h, GETextureFormat format, TextureCacheVulkan::TexCacheEntry *entry) { +static inline u32 QuickTexHash(TextureReplacer &replacer, u32 addr, int bufw, int w, int h, GETextureFormat format, TextureCacheVulkan::TexCacheEntry *entry) { + if (replacer.Enabled()) { + return replacer.ComputeHash(addr, bufw, w, h, format, entry->maxSeenV); + } + if (h == 512 && entry->maxSeenV < 512 && entry->maxSeenV != 0) { h = (int)entry->maxSeenV; } @@ -874,6 +878,34 @@ bool TextureCacheVulkan::SetOffsetTexture(u32 offset) { return false; } +ReplacedTextureFormat FromVulkanFormat(VkFormat fmt) { + switch (fmt) { + case VULKAN_565_FORMAT: + return ReplacedTextureFormat::F_5650; + case VULKAN_1555_FORMAT: + return ReplacedTextureFormat::F_5551; + case VULKAN_4444_FORMAT: + return ReplacedTextureFormat::F_4444; + case VULKAN_8888_FORMAT: + default: + return ReplacedTextureFormat::F_8888; + } +} + +VkFormat ToVulkanFormat(ReplacedTextureFormat fmt) { + switch (fmt) { + case ReplacedTextureFormat::F_5650: + return VULKAN_565_FORMAT; + case ReplacedTextureFormat::F_5551: + return VULKAN_1555_FORMAT; + case ReplacedTextureFormat::F_4444: + return VULKAN_4444_FORMAT; + case ReplacedTextureFormat::F_8888: + default: + return VULKAN_8888_FORMAT; + } +} + void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { #ifdef DEBUG_TEXTURES if (SetDebugTexture()) { @@ -993,13 +1025,13 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { bool hashFail = false; if (texhash != entry->hash) { - fullhash = QuickTexHash(texaddr, bufw, w, h, format, entry); + fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); hashFail = true; rehash = false; } if (rehash && entry->GetHashStatus() != TexCacheEntry::STATUS_RELIABLE) { - fullhash = QuickTexHash(texaddr, bufw, w, h, format, entry); + fullhash = QuickTexHash(replacer, texaddr, bufw, w, h, format, entry); if (fullhash != entry->fullhash) { hashFail = true; } else { @@ -1142,7 +1174,7 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { // to avoid excessive clearing caused by cache invalidations. entry->sizeInRAM = (textureBitsPerPixel[format] * bufw * h / 2) / 8; - entry->fullhash = fullhash == 0 ? QuickTexHash(texaddr, bufw, w, h, format, entry) : fullhash; + entry->fullhash = fullhash == 0 ? QuickTexHash(replacer, texaddr, bufw, w, h, format, entry) : fullhash; entry->cluthash = cluthash; entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; @@ -1205,6 +1237,15 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { scaleFactor = scaleFactor > 4 ? 4 : (scaleFactor > 2 ? 2 : 1); } + ReplacedTexture &replaced = replacer.FindReplacement(cachekey, entry->fullhash, w, h); + if (replaced.GetSize(0, w, h)) { + // We're replacing, so we won't scale. + scaleFactor = 1; + if (g_Config.bMipMap) { + maxLevel = replaced.MaxLevel(); + } + } + // Don't scale the PPGe texture. if (entry->addr > 0x05000000 && entry->addr < 0x08800000) scaleFactor = 1; @@ -1230,6 +1271,9 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { } VkFormat actualFmt = scaleFactor > 1 ? VULKAN_8888_FORMAT : dstFmt; + if (replaced.Valid()) { + actualFmt = ToVulkanFormat(replaced.Format(0)); + } if (!entry->vkTex) { entry->vkTex = new CachedTextureVulkan(); entry->vkTex->texture_ = new VulkanTexture(vulkan_, allocator_); @@ -1285,20 +1329,45 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { } lastBoundTexture = entry->vkTex; + ReplacedTextureDecodeInfo replacedInfo; + if (replacer.Enabled() && !replaced.Valid()) { + replacedInfo.cachekey = cachekey; + replacedInfo.hash = entry->fullhash; + replacedInfo.addr = texaddr; + replacedInfo.isVideo = videos_.find(texaddr & 0x3FFFFFFF) != videos_.end(); + replacedInfo.isFinal = (entry->status & TexCacheEntry::STATUS_TO_SCALE) == 0; + replacedInfo.scaleFactor = scaleFactor; + replacedInfo.fmt = FromVulkanFormat(actualFmt); + } + if (entry->vkTex) { // Upload the texture data. for (int i = 0; i <= maxLevel; i++) { int mipWidth = gstate.getTextureWidth(i) * scaleFactor; int mipHeight = gstate.getTextureHeight(i) * scaleFactor; + if (replaced.Valid()) { + replaced.GetSize(i, mipWidth, mipHeight); + } int bpp = actualFmt == VULKAN_8888_FORMAT ? 4 : 2; int stride = (mipWidth * bpp + 15) & ~15; int size = stride * mipHeight; uint32_t bufferOffset; VkBuffer texBuf; void *data = uploadBuffer->Push(size, &bufferOffset, &texBuf); - LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt); + if (replaced.Valid()) { + replaced.Load(i, data, stride); + } else { + LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt); + if (replacer.Enabled()) { + replacer.NotifyTextureDecoded(replacedInfo, data, stride, i, mipWidth, mipHeight); + } + } entry->vkTex->texture_->UploadMip(i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp); } + + if (replaced.Valid()) { + entry->SetAlphaStatus(TexCacheEntry::Status(replaced.AlphaStatus())); + } } entry->vkTex->texture_->EndCreate(); @@ -1576,12 +1645,13 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt // We always end up at 8888. Other parts assume this. assert(dstFmt == VULKAN_8888_FORMAT); - decPitch = w * sizeof(u32); - rowBytes = w * sizeof(u32); + bpp = sizeof(u32); + decPitch = w * bpp; + rowBytes = w * bpp; } if ((entry.status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { - TexCacheEntry::Status alphaStatus = CheckAlpha(pixelData, dstFmt, bufw, w, h); + TexCacheEntry::Status alphaStatus = CheckAlpha(pixelData, dstFmt, decPitch / bpp, w, h); entry.SetAlphaStatus(alphaStatus, level); } else { entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); diff --git a/Qt/GPU.pro b/Qt/GPU.pro index e993b64b5a54..63be849c9078 100644 --- a/Qt/GPU.pro +++ b/Qt/GPU.pro @@ -61,7 +61,8 @@ SOURCES += $$P/GPU/GeDisasm.cpp \ # GPU $$P/GPU/Common/SplineCommon.cpp \ $$P/GPU/Common/DrawEngineCommon.cpp \ $$P/ext/xxhash.c \ # xxHash - $$P/ext/xbrz/*.cpp # XBRZ + $$P/ext/xbrz/*.cpp \ # XBRZ + $$P/Core/TextureReplacer.cpp # Bit of a hack. Avoids a linking issue. armv7: SOURCES += $$P/GPU/Common/TextureDecoderNEON.cpp diff --git a/Qt/Settings.pri b/Qt/Settings.pri index 83405099dcd3..0f731dec7d79 100644 --- a/Qt/Settings.pri +++ b/Qt/Settings.pri @@ -67,6 +67,11 @@ win32-msvc* { QMAKE_ALLFLAGS_RELEASE += -O3 -ffast-math } +symbian { + # Silence a common warning in system headers. + QMAKE_CXXFLAGS += -Wno-address +} + contains(QT_CONFIG, opengles.) { DEFINES += USING_GLES2 # How else do we know if the environment prefers windows? diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 6eb9e787c883..82f5adb187cd 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -268,6 +268,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/SaveState.cpp \ $(SRC)/Core/Screenshot.cpp \ $(SRC)/Core/System.cpp \ + $(SRC)/Core/TextureReplacer.cpp \ $(SRC)/Core/Debugger/Breakpoints.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ $(SRC)/Core/Dialog/PSPDialog.cpp \ diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp index 55a7b36a0fe8..b06117658c9e 100644 --- a/ext/native/ui/ui_screen.cpp +++ b/ext/native/ui/ui_screen.cpp @@ -186,7 +186,7 @@ UI::EventReturn UIScreen::OnCancel(UI::EventParams &e) { } PopupScreen::PopupScreen(std::string title, std::string button1, std::string button2) - : box_(0), title_(title), defaultButton_(NULL) { + : box_(0), defaultButton_(nullptr), title_(title) { I18NCategory *di = GetI18NCategory("Dialog"); if (!button1.empty()) button1_ = di->T(button1.c_str()); @@ -372,25 +372,25 @@ void PopupMultiChoice::Draw(UIContext &dc) { } PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) - : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1), screenManager_(screenManager), units_(units) { + : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1), units_(units), screenManager_(screenManager) { fmt_ = "%i"; OnClick.Handle(this, &PopupSliderChoice::HandleClick); } PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, int step, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) - : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), screenManager_(screenManager), units_(units) { + : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), units_(units), screenManager_(screenManager) { fmt_ = "%i"; OnClick.Handle(this, &PopupSliderChoice::HandleClick); } PopupSliderChoiceFloat::PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) - : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1.0f), screenManager_(screenManager), units_(units) { + : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1.0f), units_(units), screenManager_(screenManager) { fmt_ = "%2.2f"; OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick); } PopupSliderChoiceFloat::PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, float step, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) - : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), screenManager_(screenManager), units_(units) { + : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), units_(units), screenManager_(screenManager) { fmt_ = "%2.2f"; OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick); }