mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	nested folder support + refuse to load incompatibly sized textures + general cleanups
This commit is contained in:
		
							parent
							
								
									8a98310a16
								
							
						
					
					
						commit
						ae4aaf2fc1
					
				
					 8 changed files with 141 additions and 57 deletions
				
			
		|  | @ -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()); | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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
 | ||||
|  | @ -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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue