diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 5b460466e..aaa5f5ea4 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -164,6 +164,7 @@ void Config::ReadValues() { // Utility Settings::values.dump_textures = sdl2_config->GetBoolean("Utility", "dump_textures", false); + Settings::values.custom_textures = sdl2_config->GetBoolean("Utility", "custom_textures", false); // Audio Settings::values.enable_dsp_lle = sdl2_config->GetBoolean("Audio", "enable_dsp_lle", false); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 4a6194bc4..04e7a14b8 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -182,6 +182,10 @@ swap_screen = # 0 (default): Off, 1: On dump_textures = +# Reads PNG files from load/textures/[Title ID]/ and replaces textures. +# 0 (default): Off, 1: On +custom_textures = + [Audio] # Whether or not to enable DSP LLE # 0 (default): No, 1: Yes diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 362ac8b19..1e1727165 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -231,9 +231,11 @@ void Config::ReadControlValues() { } void Config::ReadUtilityValues() { - qt_config->beginGroup("Utility"); + Settings::values.dump_textures = ReadSetting("dump_textures", false).toBool(); + Settings::values.custom_textures = ReadSetting("custom_textures", false).toBool(); + qt_config->endGroup(); } @@ -704,6 +706,7 @@ void Config::SaveUtilityValues() { qt_config->beginGroup("Utility"); WriteSetting("dump_textures", Settings::values.dump_textures, false); + WriteSetting("custom_textures", Settings::values.custom_textures, false); qt_config->endGroup(); } diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index f74b9a211..5bfe2f993 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -88,6 +88,7 @@ void ConfigureGraphics::ApplyConfiguration() { static_cast(ui->layout_combobox->currentIndex()); 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(); Settings::values.bg_red = static_cast(bg_color.redF()); Settings::values.bg_green = static_cast(bg_color.greenF()); Settings::values.bg_blue = static_cast(bg_color.blueF()); diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index 9cac5f185..c95bbb800 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -334,6 +334,16 @@ Utility + + + + <html><head/><body><p>Replace textures with PNG files.</p><p>Textures are loaded from load/textures/[Title ID]/.</p></body></html> + + + Use Custom Textures (Hardware Renderer only) + + + @@ -347,19 +357,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/src/common/common_paths.h b/src/common/common_paths.h index b302c2b25..42f952b2c 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -45,6 +45,7 @@ #define DLL_DIR "external_dlls" #define SHADER_DIR "shaders" #define DUMP_DIR "dump" +#define LOAD_DIR "load" // Filenames // Files in the directory returned by GetUserPath(UserPath::LogDir) diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 1f7360fa6..656050fd5 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -713,6 +713,7 @@ void SetUserPath(const std::string& path) { g_paths.emplace(UserPath::DLLDir, user_path + DLL_DIR DIR_SEP); g_paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP); g_paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP); + g_paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP); } const std::string& GetUserPath(UserPath path) { diff --git a/src/common/file_util.h b/src/common/file_util.h index 62c9f36a9..6fc7e4e11 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -27,6 +27,7 @@ enum class UserPath { ConfigDir, DLLDir, DumpDir, + LoadDir, LogDir, NANDDir, RootDir, diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5ec409f1e..f6ce91f76 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -36,6 +36,8 @@ add_library(core STATIC core.h core_timing.cpp core_timing.h + custom_tex_cache.cpp + custom_tex_cache.h dumping/backend.cpp dumping/backend.h file_sys/archive_backend.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 03dfc59c9..4a1ac0b9a 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -16,10 +16,14 @@ #include "core/cheats/cheats.h" #include "core/core.h" #include "core/core_timing.h" +<<<<<<< HEAD #include "core/dumping/backend.h" #ifdef ENABLE_FFMPEG_VIDEO_DUMPER #include "core/dumping/ffmpeg_backend.h" #endif +======= +#include "core/custom_tex_cache.h" +>>>>>>> 387a49d7... fix crashes, add custom texture cache, load textures from load directory #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/kernel.h" @@ -146,12 +150,16 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st } } cheat_engine = std::make_unique(*this); +<<<<<<< HEAD u64 title_id{0}; if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", static_cast(load_result)); } perf_stats = std::make_unique(title_id); +======= + custom_tex_cache = std::make_unique(); +>>>>>>> 387a49d7... fix crashes, add custom texture cache, load textures from load directory status = ResultStatus::Success; m_emu_window = &emu_window; m_filepath = filepath; @@ -290,12 +298,21 @@ const Cheats::CheatEngine& System::CheatEngine() const { return *cheat_engine; } +<<<<<<< HEAD VideoDumper::Backend& System::VideoDumper() { return *video_dumper; } const VideoDumper::Backend& System::VideoDumper() const { return *video_dumper; +======= +Core::CustomTexCache& System::CustomTexCache() { + return *custom_tex_cache; +} + +const Core::CustomTexCache& System::CustomTexCache() const { + return *custom_tex_cache; +>>>>>>> 387a49d7... fix crashes, add custom texture cache, load textures from load directory } void System::RegisterMiiSelector(std::shared_ptr mii_selector) { diff --git a/src/core/core.h b/src/core/core.h index d4747ae36..5cd9c5ea6 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -7,6 +7,7 @@ #include #include #include "common/common_types.h" +#include "core/custom_tex_cache.h" #include "core/frontend/applets/mii_selector.h" #include "core/frontend/applets/swkbd.h" #include "core/loader/loader.h" @@ -216,7 +217,12 @@ public: /// Gets a const reference to the video dumper backend const VideoDumper::Backend& VideoDumper() const; - std::unique_ptr perf_stats; + /// Gets a reference to the custom texture cache system + Core::CustomTexCache& CustomTexCache(); + + /// Gets a const reference to the custom texture cache system + const Core::CustomTexCache& CustomTexCache() const; + FrameLimiter frame_limiter; void SetStatus(ResultStatus new_status, const char* details = nullptr) { @@ -289,6 +295,9 @@ private: /// Video dumper backend std::unique_ptr video_dumper; + /// Custom texture cache system + std::unique_ptr custom_tex_cache; + /// RPC Server for scripting support std::unique_ptr rpc_server; diff --git a/src/core/custom_tex_cache.cpp b/src/core/custom_tex_cache.cpp new file mode 100644 index 000000000..ed8eabafd --- /dev/null +++ b/src/core/custom_tex_cache.cpp @@ -0,0 +1,27 @@ +#include +#include +#include "common/common_types.h" +#include "custom_tex_cache.h" + +namespace Core { +const bool CustomTexCache::IsTextureDumped(const u64 hash) { + return dumped_textures.find(hash) != dumped_textures.end(); +} + +void CustomTexCache::SetTextureDumped(const u64 hash) { + dumped_textures[hash] = true; +} + +const bool CustomTexCache::IsTextureCached(const u64 hash) { + return custom_textures.find(hash) != custom_textures.end(); +} + +const CustomTexInfo& CustomTexCache::LookupTexture(const u64 hash) { + return custom_textures.at(hash); +} + +void CustomTexCache::CacheTexture(const u64 hash, const std::vector& tex, u32 width, + u32 height) { + custom_textures[hash] = {width, height, tex}; +} +} // namespace Core \ No newline at end of file diff --git a/src/core/custom_tex_cache.h b/src/core/custom_tex_cache.h new file mode 100644 index 000000000..a6c226fe6 --- /dev/null +++ b/src/core/custom_tex_cache.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include "common/common_types.h" + +namespace Core { +struct CustomTexInfo { + u32 width; + u32 height; + std::vector tex; +}; + +// TODO: think of a better name for this class... +class CustomTexCache { +public: + const bool IsTextureDumped(const u64 hash); + void SetTextureDumped(const u64 hash); + + const bool IsTextureCached(const u64 hash); + const CustomTexInfo& LookupTexture(const u64 hash); + void CacheTexture(const u64 hash, const std::vector& tex, u32 width, u32 height); + +private: + std::unordered_map dumped_textures; + std::unordered_map custom_textures; +}; +} // namespace Core \ No newline at end of file diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 6b28de6fb..079d276d0 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -88,6 +88,7 @@ void LogSettings() { LogSetting("Layout_LayoutOption", static_cast(Settings::values.layout_option)); LogSetting("Layout_SwapScreen", Settings::values.swap_screen); LogSetting("Utility_DumpTextures", Settings::values.dump_textures); + LogSetting("Utility_CustomTextures", Settings::values.custom_textures); LogSetting("Audio_EnableDspLle", Settings::values.enable_dsp_lle); LogSetting("Audio_EnableDspLleMultithread", Settings::values.enable_dsp_lle_multithread); LogSetting("Audio_OutputEngine", Settings::values.sink_id); diff --git a/src/core/settings.h b/src/core/settings.h index 5bbf98edd..1d00a71a6 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -171,6 +171,7 @@ struct Values { std::string pp_shader_name; bool dump_textures; + bool custom_textures; // Audio bool enable_dsp_lle; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 878f5fd16..bf6359eb5 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -24,6 +24,7 @@ #include "common/scope_exit.h" #include "common/vector_math.h" #include "core/core.h" +#include "core/custom_tex_cache.h" #include "core/frontend/emu_window.h" #include "core/hle/kernel/process.h" #include "core/memory.h" @@ -856,7 +857,7 @@ void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) { // TODO: move this function to a better place void FlipRGBA8Texture(std::vector& tex, u64 width, u64 height) { - assert(tex.size() = width * height * 4); + ASSERT(tex.size() == width * height * 4); const u64 line_size = width * 4; // Thanks MSVC for not being able to make variable length arrays u8* temp_row = new u8[line_size]; @@ -883,38 +884,60 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle& rect, GLuint r ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format)); - // Decode and dump texture if texture dumping is enabled - // or read texture and replace - bool should_dump = false; - bool should_use_custom_tex = false; - std::string dump_path; + // Read custom texture + auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache(); + bool dump_tex = false; + bool use_custom_tex = false; + std::string dump_path; // Has to be declared here for logging later std::vector decoded_png; u32 png_width; u32 png_height; - if (Settings::values.dump_textures) { - dump_path = fmt::format("{}/textures", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)); - if (!FileUtil::IsDirectory(dump_path)) - FileUtil::CreateDir(dump_path); - dump_path += fmt::format( - "/{:016X}", - Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); - if (!FileUtil::IsDirectory(dump_path)) - FileUtil::CreateDir(dump_path); - // Hash the encoded texture - const u64 tex_hash = Common::ComputeHash64(gl_buffer.get(), gl_buffer_size); - dump_path += fmt::format("/tex1_{}x{}_{:016X}_{}.png", width, height, tex_hash, - static_cast(pixel_format)); - if (!FileUtil::Exists(dump_path)) - should_dump = true; - else { - u32 lodepng_ret = lodepng::decode(decoded_png, png_width, png_height, dump_path); - if (lodepng_ret) - LOG_CRITICAL(Render_OpenGL, "Failed to load custom texture: {}", - lodepng_error_text(lodepng_ret)); - else { - FlipRGBA8Texture(decoded_png, png_width, png_height); - should_use_custom_tex = true; + u64 tex_hash = 0; + + if (Settings::values.dump_textures || Settings::values.custom_textures) + tex_hash = Common::ComputeHash64(gl_buffer.get(), gl_buffer_size); + + if (Settings::values.custom_textures) { + 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)) { + if (FileUtil::Exists(load_path)) { + u32 lodepng_ret = lodepng::decode(decoded_png, png_width, png_height, load_path); + if (lodepng_ret) + LOG_CRITICAL(Render_OpenGL, "Failed to load custom texture: {}", + lodepng_error_text(lodepng_ret)); + else { + LOG_INFO(Render_OpenGL, "Loaded custom texture from {}", load_path); + FlipRGBA8Texture(decoded_png, png_width, png_height); + custom_tex_cache.CacheTexture(tex_hash, decoded_png, png_width, png_height); + use_custom_tex = true; + } } + } else { + const auto custom_tex_info = custom_tex_cache.LookupTexture(tex_hash); + decoded_png = custom_tex_info.tex; + png_width = custom_tex_info.width; + png_height = custom_tex_info.height; + use_custom_tex = true; + } + } + + if (Settings::values.dump_textures) { + dump_path = fmt::format( + "{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), + Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); + if (!FileUtil::CreateFullPath(dump_path)) + LOG_ERROR(Render, "Unable to create {}", dump_path); + + dump_path += fmt::format("tex1_{}x{}_{:016X}_{}.png", width, height, tex_hash, + static_cast(pixel_format)); + if (!custom_tex_cache.IsTextureDumped(tex_hash) && !FileUtil::Exists(dump_path)) { + custom_tex_cache.SetTextureDumped(tex_hash); + dump_tex = true; } } @@ -929,7 +952,7 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle& rect, GLuint r // If not 1x scale, create 1x texture that we will blit from to replace texture subrect in // surface OGLTexture unscaled_tex; - if (res_scale != 1 && !should_use_custom_tex) { + if (res_scale != 1 && !use_custom_tex) { x0 = 0; y0 = 0; @@ -946,7 +969,7 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle& rect, GLuint r // Ensure no bad interactions with GL_UNPACK_ALIGNMENT ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0); - if (!should_use_custom_tex) { + if (!use_custom_tex) { glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(stride)); glActiveTexture(GL_TEXTURE0); @@ -968,11 +991,11 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle& rect, GLuint r } glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - if (should_dump) { + if (dump_tex) { // Dump texture to RGBA8 and encode as PNG LOG_INFO(Render_OpenGL, "Dumping texture to {}", dump_path); std::vector decoded_texture; - decoded_texture.resize(rect.GetWidth() * rect.GetHeight() * 4); + decoded_texture.resize(width * height * 4); glBindTexture(GL_TEXTURE_2D, target_tex); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, &decoded_texture[0]); glBindTexture(GL_TEXTURE_2D, 0); @@ -982,12 +1005,13 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle& rect, GLuint r LOG_CRITICAL(Render_OpenGL, "Failed to save decoded texture! {}", lodepng_error_text(png_error)); } + custom_tex_cache.SetTextureDumped(tex_hash); } cur_state.texture_units[0].texture_2d = old_tex; cur_state.Apply(); - if (res_scale != 1 && !should_use_custom_tex) { + if (res_scale != 1 && !use_custom_tex) { auto scaled_rect = rect; scaled_rect.left *= res_scale; scaled_rect.top *= res_scale;