diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 148089694061..fb97379411f9 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -83,10 +83,10 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/GCAdapter.h" +#include "VideoCommon/Assets/CustomAssetLoader.h" #include "VideoCommon/AsyncRequests.h" #include "VideoCommon/Fifo.h" #include "VideoCommon/FrameDumper.h" -#include "VideoCommon/HiresTextures.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/PerformanceMetrics.h" #include "VideoCommon/Present.h" @@ -546,6 +546,9 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi FreeLook::LoadInputConfig(); + system.GetCustomAssetLoader().Init(); + Common::ScopeGuard asset_loader_guard([&system] { system.GetCustomAssetLoader().Shutdown(); }); + Movie::Init(*boot); Common::ScopeGuard movie_guard{&Movie::Shutdown}; @@ -597,10 +600,6 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi return; } - // Inputs loading may have generated custom dynamic textures - // it's now ok to initialize any custom textures - HiresTexture::Update(); - AudioCommon::PostInitSoundStream(system); // The hardware is initialized. diff --git a/Source/Core/Core/System.cpp b/Source/Core/Core/System.cpp index eecb590d5cb1..aeb81e5122e4 100644 --- a/Source/Core/Core/System.cpp +++ b/Source/Core/Core/System.cpp @@ -27,6 +27,7 @@ #include "Core/PowerPC/PowerPC.h" #include "IOS/USB/Emulated/Infinity.h" #include "IOS/USB/Emulated/Skylander.h" +#include "VideoCommon/Assets/CustomAssetLoader.h" #include "VideoCommon/CommandProcessor.h" #include "VideoCommon/Fifo.h" #include "VideoCommon/GeometryShaderManager.h" @@ -79,6 +80,7 @@ struct System::Impl VideoInterface::VideoInterfaceManager m_video_interface; Interpreter m_interpreter; JitInterface m_jit_interface; + VideoCommon::CustomAssetLoader m_custom_asset_loader; }; System::System() : m_impl{std::make_unique(*this)} @@ -263,4 +265,9 @@ VideoInterface::VideoInterfaceManager& System::GetVideoInterface() const { return m_impl->m_video_interface; } + +VideoCommon::CustomAssetLoader& System::GetCustomAssetLoader() const +{ + return m_impl->m_custom_asset_loader; +} } // namespace Core diff --git a/Source/Core/Core/System.h b/Source/Core/Core/System.h index 8d00a3ec5ec7..a8732eb10686 100644 --- a/Source/Core/Core/System.h +++ b/Source/Core/Core/System.h @@ -85,6 +85,10 @@ namespace SerialInterface { class SerialInterfaceManager; }; +namespace VideoCommon +{ +class CustomAssetLoader; +} namespace VideoInterface { class VideoInterfaceManager; @@ -152,6 +156,7 @@ class System Sram& GetSRAM() const; VertexShaderManager& GetVertexShaderManager() const; VideoInterface::VideoInterfaceManager& GetVideoInterface() const; + VideoCommon::CustomAssetLoader& GetCustomAssetLoader() const; private: System(); diff --git a/Source/Core/InputCommon/DynamicInputTextureManager.cpp b/Source/Core/InputCommon/DynamicInputTextureManager.cpp index d3b47089a670..fade2c05d431 100644 --- a/Source/Core/InputCommon/DynamicInputTextureManager.cpp +++ b/Source/Core/InputCommon/DynamicInputTextureManager.cpp @@ -42,13 +42,9 @@ void DynamicInputTextureManager::Load() void DynamicInputTextureManager::GenerateTextures(const Common::IniFile& file, const std::vector& controller_names) { - bool any_dirty = false; for (const auto& configuration : m_configuration) { - any_dirty |= configuration.GenerateTextures(file, controller_names); + (void)configuration.GenerateTextures(file, controller_names); } - - if (any_dirty && g_texture_cache && Core::GetState() != Core::State::Starting) - g_texture_cache->ForceReloadTextures(); } } // namespace InputCommon diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index 0227105d1919..27fc75ec4d9b 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -19,38 +19,67 @@ #include "Common/CommonPaths.h" #include "Common/FileSearch.h" #include "Common/FileUtil.h" -#include "Common/Flag.h" -#include "Common/IOFile.h" -#include "Common/Image.h" #include "Common/Logging/Log.h" -#include "Common/MemoryUtil.h" #include "Common/StringUtil.h" -#include "Common/Swap.h" -#include "Common/Thread.h" -#include "Common/Timer.h" #include "Core/Config/GraphicsSettings.h" #include "Core/ConfigManager.h" +#include "Core/System.h" +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/CustomAssetLoader.h" +#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoConfig.h" -struct DiskTexture +constexpr std::string_view s_format_prefix{"tex1_"}; + +static std::unordered_map> s_hires_texture_cache; +static std::unordered_map s_hires_texture_id_to_arbmipmap; + +static auto s_file_library = std::make_shared(); + +namespace { - std::string path; - bool has_arbitrary_mipmaps; -}; +std::pair GetNameArbPair(const TextureInfo& texture_info) +{ + if (s_hires_texture_id_to_arbmipmap.empty()) + return {"", false}; -constexpr std::string_view s_format_prefix{"tex1_"}; + 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}; + } -static std::unordered_map s_textureMap; -static std::unordered_map> s_textureCache; -static std::mutex s_textureCacheMutex; -static Common::Flag s_textureCacheAbortLoading; + // 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}; + } -static std::thread s_prefetcher; + // 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::Init() { - // Note: Update is not called here so that we handle dynamic textures on startup more gracefully + Update(); } void HiresTexture::Shutdown() @@ -60,28 +89,19 @@ void HiresTexture::Shutdown() void HiresTexture::Update() { - if (s_prefetcher.joinable()) - { - s_textureCacheAbortLoading.Set(); - s_prefetcher.join(); - } - if (!g_ActiveConfig.bHiresTextures) { Clear(); return; } - if (!g_ActiveConfig.bCacheHiresTextures) - { - s_textureCache.clear(); - } - const std::string& game_id = SConfig::GetInstance().GetGameID(); const std::set texture_directories = GetTextureDirectoriesWithGameId(File::GetUserPath(D_HIRESTEXTURES_IDX), game_id); const std::vector extensions{".png", ".dds"}; + auto& system = Core::System::GetInstance(); + for (const auto& texture_directory : texture_directories) { const auto texture_paths = @@ -100,8 +120,20 @@ void HiresTexture::Update() if (has_arbitrary_mipmaps) filename.erase(arb_index, 4); + // 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{{"", path}}); + + if (g_ActiveConfig.bCacheHiresTextures) + { + auto hires_texture = std::make_shared( + has_arbitrary_mipmaps, + system.GetCustomAssetLoader().LoadGameTexture(filename, s_file_library)); + s_hires_texture_cache.try_emplace(filename, std::move(hires_texture)); + } const auto [it, inserted] = - s_textureMap.try_emplace(filename, DiskTexture{path, has_arbitrary_mipmaps}); + s_hires_texture_id_to_arbmipmap.try_emplace(filename, has_arbitrary_mipmaps); if (!inserted) { failed_insert = true; @@ -115,269 +147,43 @@ void HiresTexture::Update() texture_directory); } } - - if (g_ActiveConfig.bCacheHiresTextures) - { - // remove cached but deleted textures - auto iter = s_textureCache.begin(); - while (iter != s_textureCache.end()) - { - if (s_textureMap.find(iter->first) == s_textureMap.end()) - { - iter = s_textureCache.erase(iter); - } - else - { - iter++; - } - } - - s_textureCacheAbortLoading.Clear(); - s_prefetcher = std::thread(Prefetch); - } } void HiresTexture::Clear() { - if (s_prefetcher.joinable()) - { - s_textureCacheAbortLoading.Set(); - s_prefetcher.join(); - } - s_textureMap.clear(); - s_textureCache.clear(); -} - -void HiresTexture::Prefetch() -{ - Common::SetCurrentThreadName("Prefetcher"); - - size_t size_sum = 0; - const size_t sys_mem = Common::MemPhysical(); - const size_t recommended_min_mem = 2 * size_t(1024 * 1024 * 1024); - // keep 2GB memory for system stability if system RAM is 4GB+ - use half of memory in other cases - const size_t max_mem = - (sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem); - - Common::Timer timer; - timer.Start(); - for (const auto& entry : s_textureMap) - { - const std::string& base_filename = entry.first; - - if (base_filename.find("_mip") == std::string::npos) - { - std::unique_lock lk(s_textureCacheMutex); - - auto iter = s_textureCache.find(base_filename); - if (iter == s_textureCache.end()) - { - // unlock while loading a texture. This may result in a race condition where - // we'll load a texture twice, but it reduces the stuttering a lot. - lk.unlock(); - std::unique_ptr texture = Load(base_filename, 0, 0); - lk.lock(); - if (texture) - { - std::shared_ptr ptr(std::move(texture)); - iter = s_textureCache.insert(iter, std::make_pair(base_filename, ptr)); - } - } - if (iter != s_textureCache.end()) - { - for (const VideoCommon::CustomTextureData::Level& l : iter->second->m_data.m_levels) - size_sum += l.data.size(); - } - } - - if (s_textureCacheAbortLoading.IsSet()) - { - return; - } - - if (size_sum > max_mem) - { - Config::SetCurrent(Config::GFX_HIRES_TEXTURES, false); - - OSD::AddMessage( - fmt::format( - "Custom Textures prefetching after {:.1f} MB aborted, not enough RAM available", - size_sum / (1024.0 * 1024.0)), - 10000); - return; - } - } - - OSD::AddMessage(fmt::format("Custom Textures loaded, {:.1f} MB in {:.1f}s", - size_sum / (1024.0 * 1024.0), timer.ElapsedMs() / 1000.0), - 10000); -} - -std::string HiresTexture::GenBaseName(const TextureInfo& texture_info, bool dump) -{ - if (!dump && s_textureMap.empty()) - return ""; - - const auto texture_name_details = texture_info.CalculateTextureName(); - - // look for an exact match first - const std::string full_name = texture_name_details.GetFullName(); - if (dump || s_textureMap.find(full_name) != s_textureMap.end()) - return full_name; - - // else try and find a wildcard - if (!dump) - { - // 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 (s_textureMap.find(texture_name_single_wildcard_tlut) != s_textureMap.end()) - return texture_name_single_wildcard_tlut; - - // 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 (s_textureMap.find(texture_name_single_wildcard_tex) != s_textureMap.end()) - return texture_name_single_wildcard_tex; - } - - return ""; + s_hires_texture_cache.clear(); + s_hires_texture_id_to_arbmipmap.clear(); + s_file_library = std::make_shared(); } std::shared_ptr HiresTexture::Search(const TextureInfo& texture_info) { - const std::string base_filename = GenBaseName(texture_info); - - std::lock_guard lk(s_textureCacheMutex); - - auto iter = s_textureCache.find(base_filename); - if (iter != s_textureCache.end()) - { - return iter->second; - } - - std::shared_ptr ptr( - Load(base_filename, texture_info.GetRawWidth(), texture_info.GetRawHeight())); - - if (ptr && g_ActiveConfig.bCacheHiresTextures) - { - s_textureCache[base_filename] = ptr; - } - - return ptr; -} - -std::unique_ptr HiresTexture::Load(const std::string& base_filename, u32 width, - u32 height) -{ - // We need to have a level 0 custom texture to even consider loading. - auto filename_iter = s_textureMap.find(base_filename); - if (filename_iter == s_textureMap.end()) - return nullptr; - - // Try to load level 0 (and any mipmaps) from a DDS file. - // If this fails, it's fine, we'll just load level0 again using SOIL. - // Can't use make_unique due to private constructor. - std::unique_ptr ret = std::unique_ptr(new HiresTexture()); - const DiskTexture& first_mip_file = filename_iter->second; - ret->m_has_arbitrary_mipmaps = first_mip_file.has_arbitrary_mipmaps; - VideoCommon::LoadDDSTexture(&ret->m_data, first_mip_file.path); - - // Load remaining mip levels, or from the start if it's not a DDS texture. - for (u32 mip_level = static_cast(ret->m_data.m_levels.size());; mip_level++) - { - std::string filename = base_filename; - if (mip_level != 0) - filename += fmt::format("_mip{}", mip_level); - - filename_iter = s_textureMap.find(filename); - if (filename_iter == s_textureMap.end()) - break; - - // Try loading DDS textures first, that way we maintain compression of DXT formats. - // TODO: Reduce the number of open() calls here. We could use one fd. - VideoCommon::CustomTextureData::Level level; - if (!LoadDDSTexture(&level, filename_iter->second.path, mip_level)) - { - if (!LoadPNGTexture(&level, filename_iter->second.path)) - { - ERROR_LOG_FMT(VIDEO, "Custom texture {} failed to load", filename); - break; - } - } - - ret->m_data.m_levels.push_back(std::move(level)); - } - - // If we failed to load any mip levels, we can't use this texture at all. - if (ret->m_data.m_levels.empty()) + const auto [base_filename, has_arb_mipmaps] = GetNameArbPair(texture_info); + if (base_filename == "") return nullptr; - // Verify that the aspect ratio of the texture hasn't changed, as this could have side-effects. - const VideoCommon::CustomTextureData::Level& first_mip = ret->m_data.m_levels[0]; - if (first_mip.width * height != first_mip.height * width) + if (auto iter = s_hires_texture_cache.find(base_filename); iter != s_hires_texture_cache.end()) { - ERROR_LOG_FMT(VIDEO, - "Invalid custom texture size {}x{} for texture {}. The aspect differs " - "from the native size {}x{}.", - first_mip.width, first_mip.height, first_mip_file.path, width, height); - } - - // Same deal if the custom texture isn't a multiple of the native size. - if (width != 0 && height != 0 && (first_mip.width % width || first_mip.height % height)) - { - ERROR_LOG_FMT(VIDEO, - "Invalid custom texture size {}x{} for texture {}. Please use an integer " - "upscaling factor based on the native size {}x{}.", - first_mip.width, first_mip.height, first_mip_file.path, width, height); + return iter->second; } - - // Verify that each mip level is the correct size (divide by 2 each time). - u32 current_mip_width = first_mip.width; - u32 current_mip_height = first_mip.height; - for (u32 mip_level = 1; mip_level < static_cast(ret->m_data.m_levels.size()); mip_level++) + else { - if (current_mip_width != 1 || current_mip_height != 1) - { - current_mip_width = std::max(current_mip_width / 2, 1u); - current_mip_height = std::max(current_mip_height / 2, 1u); - - const VideoCommon::CustomTextureData::Level& level = ret->m_data.m_levels[mip_level]; - if (current_mip_width == level.width && current_mip_height == level.height) - continue; - - ERROR_LOG_FMT( - VIDEO, "Invalid custom texture size {}x{} for texture {}. Mipmap level {} must be {}x{}.", - level.width, level.height, first_mip_file.path, mip_level, current_mip_width, - current_mip_height); - } - else + auto& system = Core::System::GetInstance(); + auto hires_texture = std::make_shared( + has_arb_mipmaps, + system.GetCustomAssetLoader().LoadGameTexture(base_filename, s_file_library)); + if (g_ActiveConfig.bCacheHiresTextures) { - // It is invalid to have more than a single 1x1 mipmap. - ERROR_LOG_FMT(VIDEO, "Custom texture {} has too many 1x1 mipmaps. Skipping extra levels.", - first_mip_file.path); + s_hires_texture_cache.try_emplace(base_filename, hires_texture); } - - // Drop this mip level and any others after it. - while (ret->m_data.m_levels.size() > mip_level) - ret->m_data.m_levels.pop_back(); - } - - // All levels have to have the same format. - if (std::any_of(ret->m_data.m_levels.begin(), ret->m_data.m_levels.end(), - [&ret](const VideoCommon::CustomTextureData::Level& l) { - return l.format != ret->m_data.m_levels[0].format; - })) - { - ERROR_LOG_FMT(VIDEO, "Custom texture {} has inconsistent formats across mip levels.", - first_mip_file.path); - - return nullptr; + return hires_texture; } +} - return ret; +HiresTexture::HiresTexture(bool has_arbitrary_mipmaps, + std::shared_ptr asset) + : m_has_arbitrary_mipmaps(has_arbitrary_mipmaps), m_game_texture(std::move(asset)) +{ } std::set GetTextureDirectoriesWithGameId(const std::string& root_directory, @@ -425,17 +231,3 @@ std::set GetTextureDirectoriesWithGameId(const std::string& root_di return result; } - -HiresTexture::~HiresTexture() -{ -} - -AbstractTextureFormat HiresTexture::GetFormat() const -{ - return m_data.m_levels.at(0).format; -} - -bool HiresTexture::HasArbitraryMipmaps() const -{ - return m_has_arbitrary_mipmaps; -} diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h index 0c0ea2adad70..ea370f27415e 100644 --- a/Source/Core/VideoCommon/HiresTextures.h +++ b/Source/Core/VideoCommon/HiresTextures.h @@ -10,6 +10,7 @@ #include "Common/CommonTypes.h" #include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/Assets/TextureAsset.h" #include "VideoCommon/TextureConfig.h" #include "VideoCommon/TextureInfo.h" @@ -25,26 +26,14 @@ class HiresTexture static void Update(); static void Clear(); static void Shutdown(); - static std::shared_ptr Search(const TextureInfo& texture_info); - static std::string GenBaseName(const TextureInfo& texture_info, bool dump = false); - - ~HiresTexture(); - - AbstractTextureFormat GetFormat() const; - bool HasArbitraryMipmaps() const; + HiresTexture(bool has_arbitrary_mipmaps, std::shared_ptr asset); - VideoCommon::CustomTextureData& GetData() { return m_data; } - const VideoCommon::CustomTextureData& GetData() const { return m_data; } + bool HasArbitraryMipmaps() const { return m_has_arbitrary_mipmaps; } + const std::shared_ptr& GetAsset() const { return m_game_texture; } private: - static std::unique_ptr Load(const std::string& base_filename, u32 width, - u32 height); - static void Prefetch(); - - HiresTexture() = default; - - VideoCommon::CustomTextureData m_data; bool m_has_arbitrary_mipmaps = false; + std::shared_ptr m_game_texture; }; diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 5b0bde53ca66..fc5c26b5a824 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -145,17 +145,6 @@ void TextureCacheBase::Invalidate() texture_pool.clear(); } -void TextureCacheBase::ForceReload() -{ - Invalidate(); - - // Clear all current hires textures, they are invalid - HiresTexture::Clear(); - - // Load fresh - HiresTexture::Update(); -} - void TextureCacheBase::OnConfigChanged(const VideoConfig& config) { if (config.bHiresTextures != backup_config.hires_textures || @@ -272,6 +261,15 @@ void TextureCacheBase::SetBackupConfig(const VideoConfig& config) config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0; } +bool TextureCacheBase::DidLinkedAssetsChange(const TCacheEntry& entry) +{ + if (!entry.linked_asset.m_asset) + return false; + + const auto last_asset_write_time = entry.linked_asset.m_asset->GetLastLoadedTime(); + return last_asset_write_time > entry.linked_asset.m_last_write_time; +} + RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette, TLUTFormat tlutfmt) { @@ -772,17 +770,10 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) void TextureCacheBase::OnFrameEnd() { - if (m_force_reload_textures.TestAndClear()) - { - ForceReload(); - } - else - { - // Flush any outstanding EFB copies to RAM, in case the game is running at an uncapped frame - // rate and not waiting for vblank. Otherwise, we'd end up with a huge list of pending - // copies. - FlushEFBCopies(); - } + // Flush any outstanding EFB copies to RAM, in case the game is running at an uncapped frame + // rate and not waiting for vblank. Otherwise, we'd end up with a huge list of pending + // copies. + FlushEFBCopies(); Cleanup(g_presenter->FrameCount()); } @@ -1271,9 +1262,26 @@ class ArbitraryMipmapDetector }; TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture_info) +{ + if (auto entry = LoadImpl(texture_info, false)) + { + if (!DidLinkedAssetsChange(*entry)) + { + return entry; + } + + InvalidateTexture(GetTexCacheIter(entry)); + return LoadImpl(texture_info, true); + } + + return nullptr; +} + +TCacheEntry* TextureCacheBase::LoadImpl(const TextureInfo& texture_info, bool force_reload) { // if this stage was not invalidated by changes to texture registers, keep the current texture - if (TMEM::IsValid(texture_info.GetStage()) && bound_textures[texture_info.GetStage()]) + if (!force_reload && TMEM::IsValid(texture_info.GetStage()) && + bound_textures[texture_info.GetStage()]) { TCacheEntry* entry = bound_textures[texture_info.GetStage()].get(); // If the TMEM configuration is such that this texture is more or less guaranteed to still @@ -1582,7 +1590,8 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp InvalidateTexture(oldest_entry); } - VideoCommon::CustomTextureData* data = nullptr; + CachedTextureAsset cached_texture_asset; + std::shared_ptr data = nullptr; bool has_arbitrary_mipmaps = false; std::shared_ptr hires_texture; if (g_ActiveConfig.bHiresTextures) @@ -1590,19 +1599,31 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp hires_texture = HiresTexture::Search(texture_info); if (hires_texture) { - data = &hires_texture->GetData(); + data = hires_texture->GetAsset()->GetData(); + cached_texture_asset = {hires_texture->GetAsset(), + hires_texture->GetAsset()->GetLastLoadedTime()}; has_arbitrary_mipmaps = hires_texture->HasArbitraryMipmaps(); + if (data) + { + if (!hires_texture->GetAsset()->Validate(texture_info.GetRawWidth(), + texture_info.GetRawHeight())) + { + data = nullptr; + } + } } } - return CreateTextureEntry( + auto entry = CreateTextureEntry( TextureCreationInfo{base_hash, full_hash, bytes_per_block, palette_size}, texture_info, - textureCacheSafetyColorSampleSize, data, has_arbitrary_mipmaps); + textureCacheSafetyColorSampleSize, data.get(), has_arbitrary_mipmaps); + entry->linked_asset = std::move(cached_texture_asset); + return entry; } RcTcacheEntry TextureCacheBase::CreateTextureEntry( const TextureCreationInfo& creation_info, const TextureInfo& texture_info, - const int safety_color_sample_size, VideoCommon::CustomTextureData* custom_texture_data, + const int safety_color_sample_size, const VideoCommon::CustomTextureData* custom_texture_data, const bool custom_arbitrary_mipmaps) { #ifdef __APPLE__ @@ -1741,7 +1762,7 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry( if (g_ActiveConfig.bDumpTextures) { - const std::string basename = HiresTexture::GenBaseName(texture_info, true); + const std::string basename = texture_info.CalculateTextureName().GetFullName(); for (u32 level = 0; level < texLevels; ++level) { DumpTexture(entry, basename, level, entry->has_arbitrary_mips); diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index 10018732b1d8..766b21bc80a0 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -35,7 +36,8 @@ struct VideoConfig; namespace VideoCommon { class CustomTextureData; -} +class GameTextureAsset; +} // namespace VideoCommon constexpr std::string_view EFB_DUMP_PREFIX = "efb1"; constexpr std::string_view XFB_DUMP_PREFIX = "xfb1"; @@ -113,6 +115,12 @@ struct fmt::formatter } }; +struct CachedTextureAsset +{ + std::shared_ptr m_asset; + std::optional m_last_write_time; +}; + struct TCacheEntry { // common members @@ -164,6 +172,8 @@ struct TCacheEntry std::string texture_info_name = ""; + CachedTextureAsset linked_asset; + explicit TCacheEntry(std::unique_ptr tex, std::unique_ptr fb); @@ -262,7 +272,6 @@ class TextureCacheBase void Shutdown(); void OnConfigChanged(const VideoConfig& config); - void ForceReload(); // Removes textures which aren't used for more than TEXTURE_KILL_THRESHOLD frames, // frameCount is the current frame number. @@ -303,9 +312,6 @@ class TextureCacheBase static bool AllCopyFilterCoefsNeeded(const std::array& coefficients); static bool CopyFilterCanOverflow(const std::array& coefficients); - // Will forcibly reload all textures when the frame next ends - void ForceReloadTextures() { m_force_reload_textures.Set(); } - protected: // Decodes the specified data to the GPU texture specified by entry. // Returns false if the configuration is not supported. @@ -337,13 +343,17 @@ class TextureCacheBase using TexPool = std::unordered_multimap; + static bool DidLinkedAssetsChange(const TCacheEntry& entry); + + TCacheEntry* LoadImpl(const TextureInfo& texture_info, bool force_reload); + bool CreateUtilityTextures(); void SetBackupConfig(const VideoConfig& config); RcTcacheEntry CreateTextureEntry(const TextureCreationInfo& creation_info, const TextureInfo& texture_info, int safety_color_sample_size, - VideoCommon::CustomTextureData* custom_texture_data, + const VideoCommon::CustomTextureData* custom_texture_data, bool custom_arbitrary_mipmaps); RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride); @@ -454,7 +464,6 @@ class TextureCacheBase void OnFrameEnd(); - Common::Flag m_force_reload_textures; Common::EventHook m_frame_event = AfterFrameEvent::Register([this] { OnFrameEnd(); }, "TextureCache"); };