Files
LibretroAdmin cd38818e52 Common: pointer and size instead of spans, loops instead of STL algorithms
SHA1, HMAC, hex dumps, PNG loading, file search, DHCP options, HTTP
posts and DirectIOFile reads and writes take pointer plus size; callers
pass their buffers straight through. Contains.h is gone; lookups are
plain loops. File search and FAT FST sorting are in-place heap sorts;
min/max/clamp/copy/fill become ternaries, memcpy and memset. Watch
string import and export had no callers and are removed.
2026-11-07 13:37:26 +01:00

260 lines
8.5 KiB
C++

// Copyright 2009 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/HiresTextures.h"
#include <fmt/format.h>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include "Common/CommonPaths.h"
#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/System.h"
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
#include "VideoCommon/Resources/CustomResourceManager.h"
#include "VideoCommon/VideoConfig.h"
#include "Core/Core.h"
constexpr std::string_view s_format_prefix{"tex1_"};
static std::unordered_map<std::string, std::shared_ptr<HiresTexture>> s_hires_texture_cache;
static std::unordered_map<std::string, bool> s_hires_texture_id_to_arbmipmap;
static auto s_file_library = std::make_shared<VideoCommon::DirectFilesystemAssetLibrary>();
namespace
{
std::pair<std::string, bool> GetNameArbPair(const TextureInfo& texture_info)
{
if (s_hires_texture_id_to_arbmipmap.empty())
return {"", false};
const auto texture_name_details = texture_info.CalculateTextureName();
// look for an exact match first
const std::string full_name = texture_name_details.GetFullName();
if (auto iter = s_hires_texture_id_to_arbmipmap.find(full_name);
iter != s_hires_texture_id_to_arbmipmap.end())
{
return {full_name, iter->second};
}
// Single wildcard ignoring the tlut hash
const std::string texture_name_single_wildcard_tlut =
fmt::format("{}_{}_$_{}", texture_name_details.base_name, texture_name_details.texture_name,
texture_name_details.format_name);
if (auto iter = s_hires_texture_id_to_arbmipmap.find(texture_name_single_wildcard_tlut);
iter != s_hires_texture_id_to_arbmipmap.end())
{
return {texture_name_single_wildcard_tlut, iter->second};
}
// Single wildcard ignoring the texture hash
const std::string texture_name_single_wildcard_tex =
fmt::format("{}_${}_{}", texture_name_details.base_name, texture_name_details.tlut_name,
texture_name_details.format_name);
if (auto iter = s_hires_texture_id_to_arbmipmap.find(texture_name_single_wildcard_tex);
iter != s_hires_texture_id_to_arbmipmap.end())
{
return {texture_name_single_wildcard_tex, iter->second};
}
return {"", false};
}
} // namespace
void HiresTexture::Shutdown()
{
Clear();
}
void HiresTexture::Update()
{
if (!g_ActiveConfig.bHiresTextures)
{
Clear();
return;
}
const std::set<std::string> texture_directories = GetTextureDirectoriesForFirstMatchingGameId(
File::GetUserPath(D_HIRESTEXTURES_IDX), SConfig::GetInstance().GetGameIDsForTextures());
constexpr auto extensions = std::to_array<std::string_view>({".png", ".dds"});
for (const auto& texture_directory : texture_directories)
{
// Watch this directory for any texture reloads
s_file_library->Watch(texture_directory);
const std::string_view texture_directory_view{texture_directory};
const auto texture_paths =
Common::DoFileSearch(&texture_directory_view, 1, extensions.data(), extensions.size(),
/*recursive*/ true);
bool failed_insert = false;
for (auto& path : texture_paths)
{
std::string filename;
SplitPath(path, nullptr, &filename, nullptr);
if (filename.substr(0, s_format_prefix.length()) == s_format_prefix)
{
const size_t arb_index = filename.rfind("_arb");
const bool has_arbitrary_mipmaps = arb_index != std::string::npos;
if (has_arbitrary_mipmaps)
filename.erase(arb_index, 4);
const auto [it, inserted] =
s_hires_texture_id_to_arbmipmap.try_emplace(filename, has_arbitrary_mipmaps);
if (!inserted)
{
failed_insert = true;
}
else
{
// Since this is just a texture (single file) the mapper doesn't really matter
// just provide a string
s_file_library->SetAssetIDMapData(filename, std::map<std::string, std::filesystem::path>{
{"texture", StringToPath(path)}});
if (g_ActiveConfig.bCacheHiresTextures)
{
auto hires_texture =
std::make_shared<HiresTexture>(has_arbitrary_mipmaps, std::move(filename));
static_cast<void>(hires_texture->LoadTexture());
s_hires_texture_cache.try_emplace(hires_texture->GetId(), hires_texture);
}
}
}
}
if (failed_insert)
{
ERROR_LOG_FMT(VIDEO, "One or more textures at path '{}' were already inserted",
texture_directory);
}
}
const std::vector<std::string> game_ids_for_textures =
SConfig::GetInstance().GetGameIDsForTextures();
const std::string game_id_display = fmt::format("{}", fmt::join(game_ids_for_textures, "' or '"));
std::string message;
if (g_ActiveConfig.bCacheHiresTextures)
{
message = fmt::format("Preloading '{}' custom textures for '{}'", s_hires_texture_cache.size(),
game_id_display);
}
else
{
message = fmt::format("Found '{}' custom textures for '{}'",
s_hires_texture_id_to_arbmipmap.size(), game_id_display);
}
Core::DisplayMessage(message, 10000);
}
void HiresTexture::Clear()
{
s_hires_texture_cache.clear();
s_hires_texture_id_to_arbmipmap.clear();
s_file_library = std::make_shared<VideoCommon::DirectFilesystemAssetLibrary>();
}
std::shared_ptr<HiresTexture> HiresTexture::Search(const TextureInfo& texture_info)
{
auto [base_filename, has_arb_mipmaps] = GetNameArbPair(texture_info);
if (base_filename == "")
return nullptr;
if (auto iter = s_hires_texture_cache.find(base_filename); iter != s_hires_texture_cache.end())
{
return iter->second;
}
else
{
auto hires_texture = std::make_shared<HiresTexture>(has_arb_mipmaps, std::move(base_filename));
if (g_ActiveConfig.bCacheHiresTextures)
{
s_hires_texture_cache.try_emplace(hires_texture->GetId(), hires_texture);
}
return hires_texture;
}
}
HiresTexture::HiresTexture(bool has_arbitrary_mipmaps, std::string id)
: m_has_arbitrary_mipmaps(has_arbitrary_mipmaps), m_id(std::move(id))
{
}
VideoCommon::TextureDataResource* HiresTexture::LoadTexture() const
{
auto& system = Core::System::GetInstance();
auto& custom_resource_manager = system.GetCustomResourceManager();
return custom_resource_manager.GetTextureDataFromAsset(m_id, s_file_library);
}
std::set<std::string> GetTextureDirectoriesWithGameId(const std::string& root_directory,
const std::string& game_id)
{
std::set<std::string> result;
const std::string texture_directory = root_directory + game_id;
if (File::Exists(texture_directory))
{
result.insert(texture_directory);
}
else
{
// If there's no directory with the region-specific ID, look for a 3-character region-free one
const std::string region_free_directory = root_directory + game_id.substr(0, 3);
if (File::Exists(region_free_directory))
{
result.insert(region_free_directory);
}
}
const auto match_gameid_or_all = [game_id](const std::string& filename) {
std::string basename;
SplitPath(filename, nullptr, &basename, nullptr);
return basename == game_id || basename == game_id.substr(0, 3) || basename == "all";
};
// Look for any other directories that might be specific to the given gameid
const auto files = Common::DoFileSearch(root_directory, ".txt", true);
for (const auto& file : files)
{
if (match_gameid_or_all(file))
{
// The following code is used to calculate the top directory
// of a found gameid.txt file
// ex: <root directory>/My folder/gameids/<gameid>.txt
// would insert "<root directory>/My folder"
const auto directory_path = file.substr(root_directory.size());
const std::size_t first_path_separator_position = directory_path.find_first_of(DIR_SEP_CHR);
result.insert(root_directory + directory_path.substr(0, first_path_separator_position));
}
}
return result;
}
std::set<std::string>
GetTextureDirectoriesForFirstMatchingGameId(const std::string& root_directory,
const std::vector<std::string>& game_ids)
{
for (const auto& game_id : game_ids)
{
auto directories = GetTextureDirectoriesWithGameId(root_directory, game_id);
if (!directories.empty())
return directories;
}
return {};
}