summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2025-06-07 15:51:34 -0400
committerGitHub <noreply@github.com>2025-06-07 15:51:34 -0400
commit52fcdde4852ccab197092da37b00548e33844d85 (patch)
tree02b438f5d1fa1c127f73bdb0a0f12c2678965435 /Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
parent5eb61024c6e94b949ce21eac81003b43587fc155 (diff)
parentc3d3b8153388866117334cdc719889d1f676a16e (diff)
Merge pull request #13386 from iwubcode/resource_manager_system
VideoCommon: add resource manager, tracks resources to load assets in optimal way and manage memory
Diffstat (limited to 'Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp')
-rw-r--r--Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp207
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