mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-27 07:20:49 +00:00
Merge pull request #15489 from unknownbrackets/texreplace
Replacement: Move IO checks to saving thread
This commit is contained in:
commit
83b8211abf
@ -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) {
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user