diff options
Diffstat (limited to 'Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp')
| -rw-r--r-- | Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp | 207 |
1 files changed, 128 insertions, 79 deletions
diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp index e70faa8359..2ca227546c 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp @@ -1,108 +1,157 @@ -// Copyright 2023 Dolphin Emulator Project +// Copyright 2025 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoCommon/Assets/CustomAssetLoader.h" -#include "Common/MemoryUtil.h" -#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include <fmt/format.h> + +#include "Common/Logging/Log.h" +#include "Common/Thread.h" + +#include "UICommon/UICommon.h" namespace VideoCommon { -void CustomAssetLoader::Init() +void CustomAssetLoader::Initialize() { - m_asset_monitor_thread_shutdown.Clear(); + ResizeWorkerThreads(2); +} - 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 - m_max_memory_available = - (sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem); +void CustomAssetLoader::Shutdown() +{ + Reset(false); +} - m_asset_monitor_thread = std::thread([this]() { - Common::SetCurrentThreadName("Asset monitor"); - while (true) - { - if (m_asset_monitor_thread_shutdown.IsSet()) - { - break; - } - - std::this_thread::sleep_for(TIME_BETWEEN_ASSET_MONITOR_CHECKS); - - std::lock_guard lk(m_asset_load_lock); - for (auto& [asset_id, asset_to_monitor] : m_assets_to_monitor) - { - if (auto ptr = asset_to_monitor.lock()) - { - const auto write_time = ptr->GetLastWriteTime(); - if (write_time > ptr->GetLastLoadedTime()) - { - (void)ptr->Load(); - } - } - } - } - }); +bool CustomAssetLoader::StartWorkerThreads(u32 num_worker_threads) +{ + for (u32 i = 0; i < num_worker_threads; i++) + { + m_worker_threads.emplace_back(&CustomAssetLoader::WorkerThreadRun, this, i); + } - m_asset_load_thread.Reset("Custom Asset Loader", [this](std::weak_ptr<CustomAsset> asset) { - if (auto ptr = asset.lock()) - { - if (m_memory_exceeded) - return; - - if (ptr->Load()) - { - std::lock_guard lk(m_asset_load_lock); - const std::size_t asset_memory_size = ptr->GetByteSizeInMemory(); - m_total_bytes_loaded += asset_memory_size; - m_assets_to_monitor.try_emplace(ptr->GetAssetId(), ptr); - if (m_total_bytes_loaded > m_max_memory_available) - { - ERROR_LOG_FMT(VIDEO, - "Asset memory exceeded with asset '{}', future assets won't load until " - "memory is available.", - ptr->GetAssetId()); - m_memory_exceeded = true; - } - } - } - }); + return HasWorkerThreads(); } -void CustomAssetLoader::Shutdown() +bool CustomAssetLoader::ResizeWorkerThreads(u32 num_worker_threads) { - m_asset_load_thread.StopAndCancel(); + if (m_worker_threads.size() == num_worker_threads) + return true; - m_asset_monitor_thread_shutdown.Set(); - m_asset_monitor_thread.join(); - m_assets_to_monitor.clear(); - m_total_bytes_loaded = 0; + StopWorkerThreads(); + return StartWorkerThreads(num_worker_threads); } -std::shared_ptr<GameTextureAsset> -CustomAssetLoader::LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr<CustomAssetLibrary> library) +bool CustomAssetLoader::HasWorkerThreads() const { - return LoadOrCreateAsset<GameTextureAsset>(asset_id, m_game_textures, std::move(library)); + return !m_worker_threads.empty(); } -std::shared_ptr<PixelShaderAsset> -CustomAssetLoader::LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr<CustomAssetLibrary> library) +void CustomAssetLoader::StopWorkerThreads() { - return LoadOrCreateAsset<PixelShaderAsset>(asset_id, m_pixel_shaders, std::move(library)); + if (!HasWorkerThreads()) + return; + + // Signal worker threads to stop, and wake all of them. + { + std::lock_guard guard(m_assets_to_load_lock); + m_exit_flag.Set(); + m_worker_thread_wake.notify_all(); + } + + // Wait for worker threads to exit. + for (std::thread& thr : m_worker_threads) + thr.join(); + m_worker_threads.clear(); + m_exit_flag.Clear(); } -std::shared_ptr<MaterialAsset> -CustomAssetLoader::LoadMaterial(const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr<CustomAssetLibrary> library) +void CustomAssetLoader::WorkerThreadRun(u32 thread_index) { - return LoadOrCreateAsset<MaterialAsset>(asset_id, m_materials, std::move(library)); + Common::SetCurrentThreadName(fmt::format("Asset Loader {}", thread_index).c_str()); + + std::unique_lock load_lock(m_assets_to_load_lock); + while (true) + { + m_worker_thread_wake.wait(load_lock, + [&] { return !m_assets_to_load.empty() || m_exit_flag.IsSet(); }); + + if (m_exit_flag.IsSet()) + return; + + // If more memory than allowed has already been loaded, we will load nothing more + // until the next ScheduleAssetsToLoad from Manager. + if (m_change_in_memory > m_allowed_memory) + { + m_assets_to_load.clear(); + continue; + } + + auto* const item = m_assets_to_load.front(); + m_assets_to_load.pop_front(); + + // Make sure another thread isn't loading this handle. + if (!m_handles_in_progress.insert(item->GetHandle()).second) + continue; + + load_lock.unlock(); + + // Unload previously loaded asset. + m_change_in_memory -= item->Unload(); + + const std::size_t bytes_loaded = item->Load(); + m_change_in_memory += s64(bytes_loaded); + + load_lock.lock(); + + { + INFO_LOG_FMT(VIDEO, "CustomAssetLoader thread {} loaded: {} ({})", thread_index, + item->GetAssetId(), UICommon::FormatSize(bytes_loaded)); + + std::lock_guard lk{m_assets_loaded_lock}; + m_asset_handles_loaded.emplace_back(item->GetHandle(), bytes_loaded > 0); + + // Make sure no other threads try to re-process this item. + // Manager will take the handles and re-ScheduleAssetsToLoad based on timestamps if needed. + std::erase(m_assets_to_load, item); + } + + m_handles_in_progress.erase(item->GetHandle()); + } } -std::shared_ptr<MeshAsset> CustomAssetLoader::LoadMesh(const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr<CustomAssetLibrary> library) +auto CustomAssetLoader::TakeLoadResults() -> LoadResults { - return LoadOrCreateAsset<MeshAsset>(asset_id, m_meshes, std::move(library)); + std::lock_guard guard(m_assets_loaded_lock); + return {std::move(m_asset_handles_loaded), m_change_in_memory.exchange(0)}; } + +void CustomAssetLoader::ScheduleAssetsToLoad(std::list<CustomAsset*> assets_to_load, + u64 allowed_memory) +{ + if (assets_to_load.empty()) [[unlikely]] + return; + + // There's new assets to process, notify worker threads + std::lock_guard guard(m_assets_to_load_lock); + m_allowed_memory = allowed_memory; + m_assets_to_load = std::move(assets_to_load); + m_worker_thread_wake.notify_all(); +} + +void CustomAssetLoader::Reset(bool restart_worker_threads) +{ + const std::size_t worker_thread_count = m_worker_threads.size(); + StopWorkerThreads(); + + m_assets_to_load.clear(); + m_asset_handles_loaded.clear(); + m_allowed_memory = 0; + m_change_in_memory = 0; + + if (restart_worker_threads) + { + StartWorkerThreads(static_cast<u32>(worker_thread_count)); + } +} + } // namespace VideoCommon |
