nested folder support + refuse to load incompatibly sized textures + general cleanups
This commit is contained in:
parent
8a98310a16
commit
ae4aaf2fc1
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <QColorDialog>
|
#include <QColorDialog>
|
||||||
#include "citra_qt/configuration/configure_enhancements.h"
|
#include "citra_qt/configuration/configure_enhancements.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "ui_configure_enhancements.h"
|
#include "ui_configure_enhancements.h"
|
||||||
#include "video_core/renderer_opengl/post_processing_opengl.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.swap_screen = ui->swap_screen->isChecked();
|
||||||
Settings::values.dump_textures = ui->toggle_dump_textures->isChecked();
|
Settings::values.dump_textures = ui->toggle_dump_textures->isChecked();
|
||||||
Settings::values.custom_textures = ui->toggle_custom_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.preload_textures = ui->toggle_preload_textures->isChecked();
|
||||||
Settings::values.bg_red = static_cast<float>(bg_color.redF());
|
Settings::values.bg_red = static_cast<float>(bg_color.redF());
|
||||||
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
|
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;
|
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) {
|
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
|
||||||
const auto callback = [recursion](u64* num_entries_out, const std::string& directory,
|
const auto callback = [recursion](u64* num_entries_out, const std::string& directory,
|
||||||
const std::string& virtual_name) -> bool {
|
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,
|
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||||
unsigned int recursion = 0);
|
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.
|
// deletes the given directory and anything under it. Returns true on success.
|
||||||
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
|
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
|
||||||
|
|
||||||
|
|
|
@ -98,47 +98,6 @@ System::ResultStatus System::SingleStep() {
|
||||||
return RunLoop(false);
|
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) {
|
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
||||||
app_loader = Loader::GetLoader(filepath);
|
app_loader = Loader::GetLoader(filepath);
|
||||||
if (!app_loader) {
|
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::CreateFullPath(fmt::format("{}textures/{:016X}/",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||||
Kernel().GetCurrentProcess()->codeset->program_id));
|
Kernel().GetCurrentProcess()->codeset->program_id));
|
||||||
|
custom_tex_cache->FindCustomTextures();
|
||||||
}
|
}
|
||||||
if (Settings::values.preload_textures)
|
if (Settings::values.preload_textures)
|
||||||
PreloadCustomTextures();
|
custom_tex_cache->PreloadTextures();
|
||||||
status = ResultStatus::Success;
|
status = ResultStatus::Success;
|
||||||
m_emu_window = &emu_window;
|
m_emu_window = &emu_window;
|
||||||
m_filepath = filepath;
|
m_filepath = filepath;
|
||||||
|
@ -238,8 +198,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
|
||||||
|
|
||||||
timing = std::make_unique<Timing>();
|
timing = std::make_unique<Timing>();
|
||||||
|
|
||||||
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing,
|
kernel = std::make_unique<Kernel::KernelSystem>(
|
||||||
[this] { PrepareReschedule(); }, system_mode);
|
*memory, *timing, [this] { PrepareReschedule(); }, system_mode);
|
||||||
|
|
||||||
if (Settings::values.use_cpu_jit) {
|
if (Settings::values.use_cpu_jit) {
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
|
|
|
@ -226,6 +226,13 @@ public:
|
||||||
|
|
||||||
/// Handles loading all custom textures from disk into cache.
|
/// Handles loading all custom textures from disk into cache.
|
||||||
void PreloadCustomTextures();
|
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;
|
FrameLimiter frame_limiter;
|
||||||
|
|
||||||
void SetStatus(ResultStatus new_status, const char* details = nullptr) {
|
void SetStatus(ResultStatus new_status, const char* details = nullptr) {
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// 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"
|
#include "core/custom_tex_cache.h"
|
||||||
|
|
||||||
namespace Core {
|
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) {
|
void CustomTexCache::CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height) {
|
||||||
custom_textures[hash] = {width, height, tex};
|
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
|
} // namespace Core
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -16,6 +17,12 @@ struct CustomTexInfo {
|
||||||
std::vector<u8> tex;
|
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...
|
// TODO: think of a better name for this class...
|
||||||
class CustomTexCache {
|
class CustomTexCache {
|
||||||
public:
|
public:
|
||||||
|
@ -29,8 +36,16 @@ public:
|
||||||
const CustomTexInfo& LookupTexture(u64 hash) const;
|
const CustomTexInfo& LookupTexture(u64 hash) const;
|
||||||
void CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height);
|
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:
|
private:
|
||||||
std::unordered_set<u64> dumped_textures;
|
std::unordered_set<u64> dumped_textures;
|
||||||
std::unordered_map<u64, CustomTexInfo> custom_textures;
|
std::unordered_map<u64, CustomTexInfo> custom_textures;
|
||||||
|
std::unordered_map<u64, CustomTexPathInfo> custom_texture_paths;
|
||||||
};
|
};
|
||||||
} // namespace Core
|
} // namespace Core
|
|
@ -860,26 +860,28 @@ bool CachedSurface::LoadCustomTexture(u64 tex_hash, Core::CustomTexInfo& tex_inf
|
||||||
bool result = false;
|
bool result = false;
|
||||||
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
|
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
|
||||||
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
|
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)) {
|
if (custom_tex_cache.IsTextureCached(tex_hash)) {
|
||||||
tex_info = custom_tex_cache.LookupTexture(tex_hash);
|
tex_info = custom_tex_cache.LookupTexture(tex_hash);
|
||||||
result = true;
|
result = true;
|
||||||
} else {
|
} 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,
|
if (image_interface->DecodePNG(tex_info.tex, tex_info.width, tex_info.height,
|
||||||
load_path)) {
|
path_info.path)) {
|
||||||
LOG_INFO(Render_OpenGL, "Loaded custom texture from {}", load_path);
|
// Make sure the texture size is a power of 2
|
||||||
Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
|
if ((ceil(log2(tex_info.width)) == floor(log2(tex_info.width))) &&
|
||||||
custom_tex_cache.CacheTexture(tex_hash, tex_info.tex, tex_info.width,
|
(ceil(log2(tex_info.height)) == floor(log2(tex_info.height)))) {
|
||||||
tex_info.height);
|
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
|
||||||
result = true;
|
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 {
|
} else {
|
||||||
LOG_CRITICAL(Render_OpenGL, "Failed to load custom texture");
|
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue