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 "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);
|
||||
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_CRITICAL(Render_OpenGL, "Failed to load custom texture");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user