nested folder support + refuse to load incompatibly sized textures + general cleanups

This commit is contained in:
Khangaroo 2019-08-16 22:34:22 -04:00 committed by James Rowe
parent 8a98310a16
commit ae4aaf2fc1
8 changed files with 141 additions and 57 deletions

View file

@ -4,6 +4,7 @@
#include <QColorDialog>
#include "citra_qt/configuration/configure_enhancements.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_enhancements.h"
#include "video_core/renderer_opengl/post_processing_opengl.h"
@ -98,6 +99,9 @@ void ConfigureEnhancements::ApplyConfiguration() {
Settings::values.swap_screen = ui->swap_screen->isChecked();
Settings::values.dump_textures = ui->toggle_dump_textures->isChecked();
Settings::values.custom_textures = ui->toggle_custom_textures->isChecked();
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
if (Settings::values.custom_textures && custom_tex_cache.IsTexturePathMapEmpty())
custom_tex_cache.FindCustomTextures();
Settings::values.preload_textures = ui->toggle_preload_textures->isChecked();
Settings::values.bg_red = static_cast<float>(bg_color.redF());
Settings::values.bg_green = static_cast<float>(bg_color.greenF());

View file

@ -469,6 +469,17 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
}
void GetAllFilesFromNestedEntries(FSTEntry& directory, std::vector<FSTEntry>& output) {
std::vector<FSTEntry> files;
for (auto& entry : directory.children) {
if (entry.isDirectory) {
GetAllFilesFromNestedEntries(entry, output);
} else {
output.push_back(entry);
}
}
}
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
const auto callback = [recursion](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {

View file

@ -115,6 +115,13 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
unsigned int recursion = 0);
/**
* Recursively searches through a FSTEntry for files, and stores them.
* @param directory The FSTEntry to start scanning from
* @param parent_entry FSTEntry vector where the results will be stored.
*/
void GetAllFilesFromNestedEntries(FSTEntry& directory, std::vector<FSTEntry>& output);
// deletes the given directory and anything under it. Returns true on success.
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);

View file

@ -98,47 +98,6 @@ System::ResultStatus System::SingleStep() {
return RunLoop(false);
}
void System::PreloadCustomTextures() {
// Custom textures are currently stored as
// load/textures/[TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
const std::string load_path =
fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
Kernel().GetCurrentProcess()->codeset->program_id);
if (FileUtil::Exists(load_path)) {
FileUtil::FSTEntry texture_files;
FileUtil::ScanDirectoryTree(load_path, texture_files);
for (const auto& file : texture_files.children) {
if (file.isDirectory)
continue;
if (file.virtualName.substr(0, 5) != "tex1_")
continue;
u32 width;
u32 height;
u64 hash;
u32 format; // unused
// TODO: more modern way of doing this
if (std::sscanf(file.virtualName.c_str(), "tex1_%ux%u_%llX_%u.png", &width, &height,
&hash, &format) == 4) {
u32 png_width;
u32 png_height;
std::vector<u8> decoded_png;
if (registered_image_interface->DecodePNG(decoded_png, png_width, png_height,
file.physicalName)) {
LOG_INFO(Render_OpenGL, "Preloaded custom texture from {}", file.physicalName);
Common::FlipRGBA8Texture(decoded_png, png_width, png_height);
custom_tex_cache->CacheTexture(hash, decoded_png, png_width, png_height);
} else {
// Error should be reported by frontend
LOG_CRITICAL(Render_OpenGL, "Failed to preload custom texture");
}
}
}
}
}
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
app_loader = Loader::GetLoader(filepath);
if (!app_loader) {
@ -200,9 +159,10 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
FileUtil::CreateFullPath(fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
Kernel().GetCurrentProcess()->codeset->program_id));
custom_tex_cache->FindCustomTextures();
}
if (Settings::values.preload_textures)
PreloadCustomTextures();
custom_tex_cache->PreloadTextures();
status = ResultStatus::Success;
m_emu_window = &emu_window;
m_filepath = filepath;
@ -238,8 +198,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
timing = std::make_unique<Timing>();
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing,
[this] { PrepareReschedule(); }, system_mode);
kernel = std::make_unique<Kernel::KernelSystem>(
*memory, *timing, [this] { PrepareReschedule(); }, system_mode);
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64

View file

@ -226,6 +226,13 @@ public:
/// Handles loading all custom textures from disk into cache.
void PreloadCustomTextures();
/// Gets a reference to the video dumper backend
VideoDumper::Backend& VideoDumper();
/// Gets a const reference to the video dumper backend
const VideoDumper::Backend& VideoDumper() const;
FrameLimiter frame_limiter;
void SetStatus(ResultStatus new_status, const char* details = nullptr) {

View file

@ -2,6 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/file_util.h"
#include "common/texture.h"
#include "core.h"
#include "core/custom_tex_cache.h"
namespace Core {
@ -28,4 +32,78 @@ const CustomTexInfo& CustomTexCache::LookupTexture(u64 hash) const {
void CustomTexCache::CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height) {
custom_textures[hash] = {width, height, tex};
}
void CustomTexCache::AddTexturePath(u64 hash, const std::string& path) {
if (custom_textures.count(hash))
LOG_ERROR(Core, "Textures {} and {} conflict!", custom_texture_paths[hash].path, path);
else
custom_texture_paths[hash] = {path, hash};
}
void CustomTexCache::FindCustomTextures() {
// Custom textures are currently stored as
// [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
const std::string load_path =
fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id);
if (FileUtil::Exists(load_path)) {
FileUtil::FSTEntry texture_dir;
std::vector<FileUtil::FSTEntry> textures;
// 64 nested folders should be plenty for most cases
FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures);
for (const auto& file : textures) {
if (file.isDirectory)
continue;
if (file.virtualName.substr(0, 5) != "tex1_")
continue;
u32 width;
u32 height;
u64 hash;
u32 format; // unused
// TODO: more modern way of doing this
if (std::sscanf(file.virtualName.c_str(), "tex1_%ux%u_%llX_%u.png", &width, &height,
&hash, &format) == 4) {
AddTexturePath(hash, file.physicalName);
}
}
}
}
void CustomTexCache::PreloadTextures() {
for (const auto& path : custom_texture_paths) {
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
const auto& path_info = path.second;
Core::CustomTexInfo tex_info;
if (image_interface->DecodePNG(tex_info.tex, tex_info.width, tex_info.height, path_info.path)) {
// Make sure the texture size is a power of 2
if ((ceil(log2(tex_info.width)) == floor(log2(tex_info.width))) &&
(ceil(log2(tex_info.height)) == floor(log2(tex_info.height)))) {
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
CacheTexture(path_info.hash, tex_info.tex, tex_info.width, tex_info.height);
} else {
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
}
} else {
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
}
}
}
bool CustomTexCache::CustomTextureExists(u64 hash) const {
return custom_texture_paths.count(hash);
}
const CustomTexPathInfo& CustomTexCache::LookupTexturePathInfo(u64 hash) const {
return custom_texture_paths.at(hash);
}
bool CustomTexCache::IsTexturePathMapEmpty() const {
return custom_texture_paths.size() == 0;
}
} // namespace Core

View file

@ -4,6 +4,7 @@
#pragma once
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -16,6 +17,12 @@ struct CustomTexInfo {
std::vector<u8> tex;
};
// This is to avoid parsing the filename multiple times
struct CustomTexPathInfo {
std::string path;
u64 hash;
};
// TODO: think of a better name for this class...
class CustomTexCache {
public:
@ -29,8 +36,16 @@ public:
const CustomTexInfo& LookupTexture(u64 hash) const;
void CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height);
void AddTexturePath(u64 hash, const std::string& path);
void FindCustomTextures();
void PreloadTextures();
bool CustomTextureExists(u64 hash) const;
const CustomTexPathInfo& LookupTexturePathInfo(u64 hash) const;
bool IsTexturePathMapEmpty() const;
private:
std::unordered_set<u64> dumped_textures;
std::unordered_map<u64, CustomTexInfo> custom_textures;
std::unordered_map<u64, CustomTexPathInfo> custom_texture_paths;
};
} // namespace Core

View file

@ -860,26 +860,28 @@ bool CachedSurface::LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_inf
bool result = false;
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
const std::string load_path =
fmt::format("{}textures/{:016X}/tex1_{}x{}_{:016X}_{}.png",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id,
width, height, tex_hash, static_cast<u32>(pixel_format));
if (custom_tex_cache.IsTextureCached(tex_hash)) {
tex_info = custom_tex_cache.LookupTexture(tex_hash);
result = true;
} else {
if (FileUtil::Exists(load_path)) {
if (custom_tex_cache.CustomTextureExists(tex_hash)) {
const auto& path_info = custom_tex_cache.LookupTexturePathInfo(tex_hash);
if (image_interface->DecodePNG(tex_info.tex, tex_info.width, tex_info.height,
load_path)) {
LOG_INFO(Render_OpenGL, "Loaded custom texture from {}", load_path);
Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
custom_tex_cache.CacheTexture(tex_hash, tex_info.tex, tex_info.width,
tex_info.height);
result = true;
path_info.path)) {
// Make sure the texture size is a power of 2
if ((ceil(log2(tex_info.width)) == floor(log2(tex_info.width))) &&
(ceil(log2(tex_info.height)) == floor(log2(tex_info.height)))) {
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
custom_tex_cache.CacheTexture(tex_hash, tex_info.tex, tex_info.width,
tex_info.height);
result = true;
} else {
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
}
} else {
LOG_CRITICAL(Render_OpenGL, "Failed to load custom texture");
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
}
}
}