Merge pull request #15489 from unknownbrackets/texreplace

Replacement: Move IO checks to saving thread
This commit is contained in:
Henrik Rydgård 2022-04-18 20:10:51 +02:00 committed by GitHub
commit 83b8211abf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 47 deletions

View File

@ -512,20 +512,50 @@ public:
int h = 0;
int pitch = 0; // bytes
Path path;
Path basePath;
std::string hashfile;
u32 replacedInfoHash;
bool skipIfExists = false;
TextureSaveTask(SimpleBuf<u32> _data) : data(std::move(_data)) {}
TaskType Type() const override { return TaskType::CPU_COMPUTE; } // Also I/O blocking but dominated by compute
void Run() override {
const Path filename = basePath / hashfile;
const Path saveFilename = basePath / NEW_TEXTURE_DIR / hashfile;
// Should we skip writing if the newly saved data already exists?
// We do this on the thread due to slow IO.
if (skipIfExists && File::Exists(saveFilename))
return;
// And we always skip if the replace file already exists.
if (File::Exists(filename))
return;
// Create subfolder as needed.
#ifdef _WIN32
size_t slash = hashfile.find_last_of("/\\");
#else
size_t slash = hashfile.find_last_of("/");
#endif
if (slash != hashfile.npos) {
// Create any directory structure as needed.
const Path saveDirectory = basePath / NEW_TEXTURE_DIR / hashfile.substr(0, slash);
if (!File::Exists(saveDirectory)) {
File::CreateFullPath(saveDirectory);
File::CreateEmptyFile(saveDirectory / ".nomedia");
}
}
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, path, 0, data.data(), pitch, nullptr);
bool success = WriteTextureToPNG(&png, saveFilename, 0, data.data(), pitch, nullptr);
png_image_free(&png);
if (png.warning_or_error >= 2) {
ERROR_LOG(COMMON, "Saving screenshot to PNG produced errors.");
@ -535,58 +565,56 @@ public:
}
};
bool TextureReplacer::WillSave(const ReplacedTextureDecodeInfo &replacedInfo) {
_assert_msg_(enabled_, "Replacement not enabled");
if (!g_Config.bSaveNewTextures)
return false;
// Don't save the PPGe texture.
if (replacedInfo.addr > 0x05000000 && replacedInfo.addr < PSP_GetKernelMemoryEnd())
return false;
if (replacedInfo.isVideo && !allowVideo_)
return false;
return true;
}
void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int w, int h) {
_assert_msg_(enabled_, "Replacement not enabled");
if (!g_Config.bSaveNewTextures) {
if (!WillSave(replacedInfo)) {
// Ignore.
return;
}
if (replacedInfo.addr > 0x05000000 && replacedInfo.addr < PSP_GetKernelMemoryEnd()) {
// Don't save the PPGe texture.
return;
}
if (replacedInfo.isVideo && !allowVideo_) {
return;
}
u64 cachekey = replacedInfo.cachekey;
if (ignoreAddress_) {
cachekey = cachekey & 0xFFFFFFFFULL;
}
if (ignoreMipmap_ && level > 0) {
return;
}
u64 cachekey = replacedInfo.cachekey;
if (ignoreAddress_) {
cachekey = cachekey & 0xFFFFFFFFULL;
}
std::string hashfile = LookupHashFile(cachekey, replacedInfo.hash, level);
const Path filename = basePath_ / hashfile;
const Path 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 (hashfile.empty()) {
// If it exists, must've been decoded and saved as a new texture already.
return;
}
ReplacementCacheKey replacementKey(cachekey, replacedInfo.hash);
auto it = savedCache_.find(replacementKey);
if (it != savedCache_.end() && File::Exists(saveFilename)) {
bool skipIfExists = false;
double now = time_now_d();
if (it != savedCache_.end()) {
// 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;
}
}
if (it->second.first.w >= w && it->second.first.h >= h) {
// If it's been more than 5 seconds, we'll check again. Maybe they deleted.
double age = now - it->second.second;
if (age < 5.0)
return;
#ifdef _WIN32
size_t slash = hashfile.find_last_of("/\\");
#else
size_t slash = hashfile.find_last_of("/");
#endif
if (slash != hashfile.npos) {
// Create any directory structure as needed.
const Path saveDirectory = basePath_ / NEW_TEXTURE_DIR / hashfile.substr(0, slash);
if (!File::Exists(saveDirectory)) {
File::CreateFullPath(saveDirectory);
File::CreateEmptyFile(saveDirectory / ".nomedia");
skipIfExists = true;
}
}
@ -600,8 +628,7 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl
SimpleBuf<u32> saveBuf;
// TODO: Move the color conversion to the thread as well.
// Actually may be better to re-decode using expand32?
// Since we're copying, change the format meanwhile. Not much extra cost.
if (replacedInfo.fmt != ReplacedTextureFormat::F_8888) {
saveBuf.resize((pitch * h) / sizeof(u16));
switch (replacedInfo.fmt) {
@ -651,8 +678,10 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl
task->w = w;
task->h = h;
task->pitch = pitch;
task->path = saveFilename;
task->basePath = basePath_;
task->hashfile = hashfile;
task->replacedInfoHash = replacedInfo.hash;
task->skipIfExists = skipIfExists;
g_threadManager.EnqueueTask(task); // We don't care about waiting for the task. It'll be fine.
// Remember that we've saved this for next time.
@ -662,7 +691,7 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl
saved.file = filename;
saved.w = w;
saved.h = h;
savedCache_[replacementKey] = saved;
savedCache_[replacementKey] = std::make_pair(saved, now);
}
void TextureReplacer::Decimate(bool forcePressure) {

View File

@ -208,6 +208,10 @@ public:
return none_;
}
// Check if a NotifyTextureDecoded for this texture is desired (used to avoid reads from write-combined memory.)
bool WillSave(const ReplacedTextureDecodeInfo &replacedInfo);
// Notify that a new texture was decoded. May already be upscaled, saves the data passed.
void NotifyTextureDecoded(const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int w, int h);
void Decimate(bool forcePressure);
@ -245,5 +249,5 @@ protected:
ReplacedTexture none_;
std::unordered_map<ReplacementCacheKey, ReplacedTexture> cache_;
std::unordered_map<ReplacementCacheKey, ReplacedTextureLevel> savedCache_;
std::unordered_map<ReplacementCacheKey, std::pair<ReplacedTextureLevel, double>> savedCache_;
};

View File

@ -794,6 +794,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
}
ReplacedTextureDecodeInfo replacedInfo;
bool willSaveTex = false;
if (replacer_.Enabled() && !replaced.Valid()) {
replacedInfo.cachekey = entry->CacheKey();
replacedInfo.hash = entry->fullhash;
@ -802,6 +803,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
replacedInfo.isFinal = (entry->status & TexCacheEntry::STATUS_TO_SCALE) == 0;
replacedInfo.scaleFactor = scaleFactor;
replacedInfo.fmt = FromVulkanFormat(actualFmt);
willSaveTex = replacer_.WillSave(replacedInfo);
}
if (entry->vkTex) {
@ -821,6 +823,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
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;
@ -829,6 +832,20 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
// NVIDIA reports a min alignment of 1 but that can't be healthy... let's align by 16 as a minimum.
int pushAlignment = std::max(16, (int)vulkan->GetPhysicalDeviceProperties().properties.limits.optimalBufferCopyOffsetAlignment);
void *data;
std::vector<uint8_t> saveData;
auto loadLevel = [&](int sz, int lstride, int lfactor) {
if (willSaveTex) {
saveData.resize(sz);
data = &saveData[0];
} else {
data = drawEngine_->GetPushBufferForTextureData()->PushAligned(sz, &bufferOffset, &texBuf, pushAlignment);
}
LoadTextureLevel(*entry, (uint8_t *)data, lstride, level, lfactor, dstFmt);
if (willSaveTex)
bufferOffset = drawEngine_->GetPushBufferForTextureData()->PushAligned(&saveData[0], sz, pushAlignment, &texBuf);
};
bool dataScaled = true;
if (replaced.Valid()) {
// Directly load the replaced image.
@ -842,19 +859,16 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT);
} else {
if (fakeMipmap) {
data = drawEngine_->GetPushBufferForTextureData()->PushAligned(size, &bufferOffset, &texBuf, pushAlignment);
LoadTextureLevel(*entry, (uint8_t *)data, stride, level, scaleFactor, dstFmt);
loadLevel(size, stride, scaleFactor);
entry->vkTex->UploadMip(cmdInit, 0, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp);
break;
} else {
if (computeUpload) {
int srcBpp = dstFmt == VULKAN_8888_FORMAT ? 4 : 2;
int srcStride = mipUnscaledWidth * srcBpp;
int srcSize = srcStride * mipUnscaledHeight;
data = drawEngine_->GetPushBufferForTextureData()->PushAligned(srcSize, &bufferOffset, &texBuf, pushAlignment);
loadLevel(srcSize, srcStride, 1);
dataScaled = false;
LoadTextureLevel(*entry, (uint8_t *)data, srcStride, i, 1, dstFmt);
// This format can be used with storage images.
VkImageView view = entry->vkTex->CreateViewForMip(i);
VkDescriptorSet descSet = computeShaderManager_.GetDescriptorSet(view, texBuf, bufferOffset, srcSize);
@ -868,8 +882,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
vulkan->Delete().QueueDeleteImageView(view);
} else {
data = drawEngine_->GetPushBufferForTextureData()->PushAligned(size, &bufferOffset, &texBuf, pushAlignment);
LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt);
loadLevel(size, stride, scaleFactor);
VK_PROFILE_BEGIN(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT,
"Copy Upload: %dx%d", mipWidth, mipHeight);
entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp);
@ -880,7 +893,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
// When hardware texture scaling is enabled, this saves the original.
int w = dataScaled ? mipWidth : mipUnscaledWidth;
int h = dataScaled ? mipHeight : mipUnscaledHeight;
// NOTE: Reading the decoded texture here may be very slow, if we just wrote it to write-combined memory.
// At this point, data should be saveData, and not slow.
replacer_.NotifyTextureDecoded(replacedInfo, data, stride, i, w, h);
}
}

View File

@ -1778,7 +1778,7 @@ void DeveloperToolsScreen::CreateViews() {
list->Add(new Choice(dev->T("Load language ini")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoadLanguageIni);
list->Add(new Choice(dev->T("Save language ini")))->OnClick.Handle(this, &DeveloperToolsScreen::OnSaveLanguageIni);
list->Add(new ItemHeader(dev->T("Texture Replacement")));
list->Add(new CheckBox(&g_Config.bSaveNewTextures, dev->T("Save new textures")));
list->Add(new CheckBox(&g_Config.bSaveNewTextures, dev->T("Save new textures")))->SetEnabledPtr(&g_Config.bReplaceTextures);
list->Add(new CheckBox(&g_Config.bReplaceTextures, dev->T("Replace textures")));
// Makes it easy to get savestates out of an iOS device. The file listing shown in MacOS doesn't allow