From ae4aaf2fc19a6f81876ebabf2d3b4412384726cb Mon Sep 17 00:00:00 2001 From: Khangaroo Date: Fri, 16 Aug 2019 22:34:22 -0400 Subject: [PATCH] nested folder support + refuse to load incompatibly sized textures + general cleanups --- .../configuration/configure_enhancements.cpp | 4 + src/common/file_util.cpp | 11 +++ src/common/file_util.h | 7 ++ src/core/core.cpp | 48 +----------- src/core/core.h | 7 ++ src/core/custom_tex_cache.cpp | 78 +++++++++++++++++++ src/core/custom_tex_cache.h | 15 ++++ .../renderer_opengl/gl_rasterizer_cache.cpp | 28 +++---- 8 files changed, 141 insertions(+), 57 deletions(-) diff --git a/src/citra_qt/configuration/configure_enhancements.cpp b/src/citra_qt/configuration/configure_enhancements.cpp index d8b00f5e0a..33fc1298fc 100644 --- a/src/citra_qt/configuration/configure_enhancements.cpp +++ b/src/citra_qt/configuration/configure_enhancements.cpp @@ -4,6 +4,7 @@ #include #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(bg_color.redF()); Settings::values.bg_green = static_cast(bg_color.greenF()); diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 656050fd53..249fbbb6f7 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -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& output) { + std::vector 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 { diff --git a/src/common/file_util.h b/src/common/file_util.h index 6fc7e4e116..45917423ad 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -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& output); + // deletes the given directory and anything under it. Returns true on success. bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256); diff --git a/src/core/core.cpp b/src/core/core.cpp index af041ac063..66e661e4a8 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -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 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(); - kernel = std::make_unique(*memory, *timing, - [this] { PrepareReschedule(); }, system_mode); + kernel = std::make_unique( + *memory, *timing, [this] { PrepareReschedule(); }, system_mode); if (Settings::values.use_cpu_jit) { #ifdef ARCHITECTURE_x86_64 diff --git a/src/core/core.h b/src/core/core.h index c8701adcc3..2042c2f15a 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -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) { diff --git a/src/core/custom_tex_cache.cpp b/src/core/custom_tex_cache.cpp index ed4ba90142..29cbf63d1e 100644 --- a/src/core/custom_tex_cache.cpp +++ b/src/core/custom_tex_cache.cpp @@ -2,6 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#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& 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 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 diff --git a/src/core/custom_tex_cache.h b/src/core/custom_tex_cache.h index 996b6834f9..8fc65c333a 100644 --- a/src/core/custom_tex_cache.h +++ b/src/core/custom_tex_cache.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -16,6 +17,12 @@ struct CustomTexInfo { std::vector 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& 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 dumped_textures; std::unordered_map custom_textures; + std::unordered_map custom_texture_paths; }; } // namespace Core \ No newline at end of file diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 490ab262c9..fae91b3c49 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -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(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); } } }