mirror of
https://github.com/citra-emu/citra.git
synced 2024-12-18 11:40:05 +00:00
fix crashes, add custom texture cache, load textures from load directory
This commit is contained in:
parent
f866b2a917
commit
6d90c42a79
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ void ConfigureGraphics::ApplyConfiguration() {
|
||||
static_cast<Settings::LayoutOption>(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<float>(bg_color.redF());
|
||||
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
|
||||
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
|
||||
|
@ -334,6 +334,16 @@
|
||||
<string>Utility</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_custom_textures">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Replace textures with PNG files.</p><p>Textures are loaded from load/textures/[Title ID]/.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Custom Textures (Hardware Renderer only)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_dump_textures">
|
||||
<property name="toolTip">
|
||||
@ -347,19 +357,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -27,6 +27,7 @@ enum class UserPath {
|
||||
ConfigDir,
|
||||
DLLDir,
|
||||
DumpDir,
|
||||
LoadDir,
|
||||
LogDir,
|
||||
NANDDir,
|
||||
RootDir,
|
||||
|
@ -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
|
||||
|
@ -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<Cheats::CheatEngine>(*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<u32>(load_result));
|
||||
}
|
||||
perf_stats = std::make_unique<PerfStats>(title_id);
|
||||
=======
|
||||
custom_tex_cache = std::make_unique<Core::CustomTexCache>();
|
||||
>>>>>>> 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<Frontend::MiiSelector> mii_selector) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#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<PerfStats> 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<VideoDumper::Backend> video_dumper;
|
||||
|
||||
/// Custom texture cache system
|
||||
std::unique_ptr<Core::CustomTexCache> custom_tex_cache;
|
||||
|
||||
/// RPC Server for scripting support
|
||||
std::unique_ptr<RPC::RPCServer> rpc_server;
|
||||
|
||||
|
27
src/core/custom_tex_cache.cpp
Normal file
27
src/core/custom_tex_cache.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#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<u8>& tex, u32 width,
|
||||
u32 height) {
|
||||
custom_textures[hash] = {width, height, tex};
|
||||
}
|
||||
} // namespace Core
|
28
src/core/custom_tex_cache.h
Normal file
28
src/core/custom_tex_cache.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
struct CustomTexInfo {
|
||||
u32 width;
|
||||
u32 height;
|
||||
std::vector<u8> 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<u8>& tex, u32 width, u32 height);
|
||||
|
||||
private:
|
||||
std::unordered_map<u64, bool> dumped_textures;
|
||||
std::unordered_map<u64, CustomTexInfo> custom_textures;
|
||||
};
|
||||
} // namespace Core
|
@ -88,6 +88,7 @@ void LogSettings() {
|
||||
LogSetting("Layout_LayoutOption", static_cast<int>(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);
|
||||
|
@ -171,6 +171,7 @@ struct Values {
|
||||
std::string pp_shader_name;
|
||||
|
||||
bool dump_textures;
|
||||
bool custom_textures;
|
||||
|
||||
// Audio
|
||||
bool enable_dsp_lle;
|
||||
|
@ -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<u8>& 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<u32>& 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<u8> 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<u32>(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<u32>(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<u32>(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<u32>& 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<u32>& 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<GLint>(stride));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@ -968,11 +991,11 @@ void CachedSurface::UploadGLTexture(const Common::Rectangle<u32>& 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<u8> 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<u32>& 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;
|
||||
|
Loading…
Reference in New Issue
Block a user