diff --git a/.gitmodules b/.gitmodules
index 64995f1c3188..54b352717be2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -84,3 +84,6 @@
[submodule "Externals/Vulkan-Headers"]
path = Externals/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers.git
+[submodule "Externals/watcher/watcher"]
+ path = Externals/watcher/watcher
+ url = https://github.com/e-dant/watcher.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b246b193b09..037242abb58b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -775,6 +775,8 @@ if (USE_RETRO_ACHIEVEMENTS)
add_subdirectory(Externals/rcheevos)
endif()
+add_subdirectory(Externals/watcher)
+
########################################
# Pre-build events: Define configuration variables and write SCM info header
#
diff --git a/Externals/watcher/CMakeLists.txt b/Externals/watcher/CMakeLists.txt
new file mode 100644
index 000000000000..097f16d64753
--- /dev/null
+++ b/Externals/watcher/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_library(watcher INTERFACE IMPORTED GLOBAL)
+set_target_properties(watcher PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/watcher/include
+)
diff --git a/Externals/watcher/watcher b/Externals/watcher/watcher
new file mode 160000
index 000000000000..0d6b9b409cca
--- /dev/null
+++ b/Externals/watcher/watcher
@@ -0,0 +1 @@
+Subproject commit 0d6b9b409ccaed6313437ea3dc8b2fc078f3d25b
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index e236276ce9c2..16062d9164a8 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -663,6 +663,8 @@
+
+
@@ -1308,6 +1310,7 @@
+
diff --git a/Source/Core/VideoCommon/Assets/CustomAsset.cpp b/Source/Core/VideoCommon/Assets/CustomAsset.cpp
index 1591f93f9138..96205b084c3c 100644
--- a/Source/Core/VideoCommon/Assets/CustomAsset.cpp
+++ b/Source/Core/VideoCommon/Assets/CustomAsset.cpp
@@ -6,8 +6,8 @@
namespace VideoCommon
{
CustomAsset::CustomAsset(std::shared_ptr library,
- const CustomAssetLibrary::AssetID& asset_id)
- : m_owning_library(std::move(library)), m_asset_id(asset_id)
+ const CustomAssetLibrary::AssetID& asset_id, u64 session_id)
+ : m_owning_library(std::move(library)), m_asset_id(asset_id), m_session_id(session_id)
{
}
@@ -34,6 +34,11 @@ const CustomAssetLibrary::TimeType& CustomAsset::GetLastLoadedTime() const
return m_last_loaded_time;
}
+std::size_t CustomAsset::GetSessionId() const
+{
+ return m_session_id;
+}
+
const CustomAssetLibrary::AssetID& CustomAsset::GetAssetId() const
{
return m_asset_id;
diff --git a/Source/Core/VideoCommon/Assets/CustomAsset.h b/Source/Core/VideoCommon/Assets/CustomAsset.h
index ea4a182932aa..85f0471ff7b8 100644
--- a/Source/Core/VideoCommon/Assets/CustomAsset.h
+++ b/Source/Core/VideoCommon/Assets/CustomAsset.h
@@ -18,7 +18,7 @@ class CustomAsset
{
public:
CustomAsset(std::shared_ptr library,
- const CustomAssetLibrary::AssetID& asset_id);
+ const CustomAssetLibrary::AssetID& asset_id, u64 session_id);
virtual ~CustomAsset() = default;
CustomAsset(const CustomAsset&) = delete;
CustomAsset(CustomAsset&&) = delete;
@@ -39,6 +39,9 @@ class CustomAsset
// Returns an id that uniquely identifies this asset
const CustomAssetLibrary::AssetID& GetAssetId() const;
+ // Returns an id that is unique to this session
+ std::size_t GetSessionId() const;
+
// A rough estimate of how much space this asset
// will take in memroy
std::size_t GetByteSizeInMemory() const;
@@ -49,6 +52,7 @@ class CustomAsset
private:
virtual CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) = 0;
CustomAssetLibrary::AssetID m_asset_id;
+ std::size_t m_session_id;
mutable std::mutex m_info_lock;
std::size_t m_bytes_loaded = 0;
diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
index 119d7444eae1..e33dff46ab0f 100644
--- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
+++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
@@ -4,13 +4,17 @@
#include "VideoCommon/Assets/CustomAssetLoader.h"
#include "Common/MemoryUtil.h"
+#include "Common/Thread.h"
+
#include "VideoCommon/Assets/CustomAssetLibrary.h"
+#include "VideoCommon/VideoEvents.h"
namespace VideoCommon
{
void CustomAssetLoader::Init()
{
- m_asset_monitor_thread_shutdown.Clear();
+ m_frame_event =
+ AfterFrameEvent::Register([this](Core::System&) { OnFrameEnd(); }, "CustomAssetLoader");
const size_t sys_mem = Common::MemPhysical();
const size_t recommended_min_mem = 2 * size_t(1024 * 1024 * 1024);
@@ -18,65 +22,12 @@ void CustomAssetLoader::Init()
m_max_memory_available =
(sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem);
- 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();
- }
- }
- }
- }
- });
-
- m_asset_load_thread.Reset("Custom Asset Loader", [this](std::weak_ptr 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;
- }
- }
- }
- });
+ ResizeWorkerThreads(2);
}
void CustomAssetLoader ::Shutdown()
{
- m_asset_load_thread.Shutdown(true);
-
- m_asset_monitor_thread_shutdown.Set();
- m_asset_monitor_thread.join();
- m_assets_to_monitor.clear();
- m_total_bytes_loaded = 0;
+ Reset(false);
}
std::shared_ptr
@@ -105,4 +56,242 @@ std::shared_ptr CustomAssetLoader::LoadMesh(const CustomAssetLibrary:
{
return LoadOrCreateAsset(asset_id, m_meshes, std::move(library));
}
+
+void CustomAssetLoader::AssetReferenced(u64 asset_session_id)
+{
+ auto& asset_load_info = m_asset_load_info[asset_session_id];
+ asset_load_info.last_xfb_seen = m_xfbs_seen;
+}
+
+void CustomAssetLoader::Reset(bool restart_worker_threads)
+{
+ const std::size_t worker_thread_count = m_worker_threads.size();
+ StopWorkerThreads();
+
+ m_game_textures.clear();
+ m_pixel_shaders.clear();
+ m_materials.clear();
+ m_meshes.clear();
+
+ m_assetid_to_asset_index.clear();
+ m_asset_load_info.clear();
+
+ {
+ std::lock_guard guard(m_reload_work_lock);
+ m_assetids_to_reload.clear();
+ }
+
+ {
+ std::lock_guard guard(m_pending_work_lock);
+ m_pending_work_per_frame.clear();
+ }
+
+ {
+ std::lock_guard guard(m_completed_work_lock);
+ m_completed_work.clear();
+ }
+
+ if (restart_worker_threads)
+ {
+ StartWorkerThreads(static_cast(worker_thread_count));
+ }
+}
+
+void CustomAssetLoader::ReloadAsset(const CustomAssetLibrary::AssetID& asset_id)
+{
+ std::lock_guard guard(m_reload_work_lock);
+ m_assetids_to_reload.push_back(asset_id);
+}
+
+bool CustomAssetLoader::StartWorkerThreads(u32 num_worker_threads)
+{
+ if (num_worker_threads == 0)
+ return true;
+
+ for (u32 i = 0; i < num_worker_threads; i++)
+ {
+ m_worker_thread_start_result.store(false);
+
+ void* thread_param = nullptr;
+ std::thread thr(&CustomAssetLoader::WorkerThreadEntryPoint, this, thread_param);
+ m_init_event.Wait();
+
+ if (!m_worker_thread_start_result.load())
+ {
+ WARN_LOG_FMT(VIDEO, "Failed to start asset load worker thread.");
+ thr.join();
+ break;
+ }
+
+ m_worker_threads.push_back(std::move(thr));
+ }
+
+ return HasWorkerThreads();
+}
+
+bool CustomAssetLoader::ResizeWorkerThreads(u32 num_worker_threads)
+{
+ if (m_worker_threads.size() == num_worker_threads)
+ return true;
+
+ StopWorkerThreads();
+ return StartWorkerThreads(num_worker_threads);
+}
+
+bool CustomAssetLoader::HasWorkerThreads() const
+{
+ return !m_worker_threads.empty();
+}
+
+void CustomAssetLoader::StopWorkerThreads()
+{
+ if (!HasWorkerThreads())
+ return;
+
+ // Signal worker threads to stop, and wake all of them.
+ {
+ std::lock_guard guard(m_pending_work_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();
+}
+
+void CustomAssetLoader::WorkerThreadEntryPoint(void* param)
+{
+ Common::SetCurrentThreadName("Asset Loader Worker");
+
+ m_worker_thread_start_result.store(true);
+ m_init_event.Set();
+
+ WorkerThreadRun();
+}
+
+void CustomAssetLoader::WorkerThreadRun()
+{
+ std::unique_lock pending_lock(m_pending_work_lock);
+ while (!m_exit_flag.IsSet())
+ {
+ m_worker_thread_wake.wait(pending_lock);
+
+ while (!m_pending_work_per_frame.empty() && !m_exit_flag.IsSet())
+ {
+ m_busy_workers++;
+
+ auto pending_iter = m_pending_work_per_frame.begin();
+ auto item(std::move(pending_iter->second));
+ m_pending_work_per_frame.erase(pending_iter);
+
+ const auto item_shared = item.lock();
+ pending_lock.unlock();
+
+ if (item_shared && m_last_frame_total_loaded_memory < m_max_memory_available)
+ {
+ if (item_shared->Load())
+ {
+ // This asset could be double counted, but will be corected on the next frame
+ m_last_frame_total_loaded_memory += item_shared->GetByteSizeInMemory();
+ }
+
+ // Regardless of whether the load was successful or not
+ // mark it as complete
+ {
+ std::lock_guard completed_guard(m_completed_work_lock);
+ m_completed_work.push_back(item_shared->GetSessionId());
+ }
+ }
+
+ pending_lock.lock();
+ m_busy_workers--;
+ }
+ }
+}
+
+void CustomAssetLoader::OnFrameEnd()
+{
+ std::vector assetids_to_reload;
+ {
+ std::lock_guard guard(m_reload_work_lock);
+ m_assetids_to_reload.swap(assetids_to_reload);
+ }
+ for (const CustomAssetLibrary::AssetID& asset_id : assetids_to_reload)
+ {
+ if (const auto asset_session_id_iter = m_assetid_to_asset_index.find(asset_id);
+ asset_session_id_iter != m_assetid_to_asset_index.end())
+ {
+ auto& asset_load_info = m_asset_load_info[asset_session_id_iter->second];
+ asset_load_info.xfb_load_request = m_xfbs_seen;
+ }
+ }
+
+ std::vector completed_work;
+ {
+ std::lock_guard guard(m_completed_work_lock);
+ m_completed_work.swap(completed_work);
+ }
+ for (std::size_t completed_index : completed_work)
+ {
+ auto& asset_load_info = m_asset_load_info[completed_index];
+
+ // If we had a load request and it wasn't from this frame, clear it
+ if (asset_load_info.xfb_load_request && asset_load_info.xfb_load_request != m_xfbs_seen)
+ {
+ asset_load_info.xfb_load_request = std::nullopt;
+ }
+ }
+
+ m_xfbs_seen++;
+
+ std::size_t total_bytes_loaded = 0;
+
+ // Build up the work prioritizing newest requested assets first
+ PendingWorkContainer new_pending_work;
+ for (const auto& asset_load_info : m_asset_load_info)
+ {
+ if (const auto asset = asset_load_info.asset.lock())
+ {
+ total_bytes_loaded += asset->GetByteSizeInMemory();
+ if (total_bytes_loaded > m_max_memory_available)
+ {
+ if (!m_memory_exceeded)
+ {
+ m_memory_exceeded = true;
+ ERROR_LOG_FMT(VIDEO,
+ "Asset memory exceeded with asset '{}', future assets won't load until "
+ "memory is available.",
+ asset->GetAssetId());
+ }
+ break;
+ }
+ if (asset_load_info.xfb_load_request)
+ {
+ new_pending_work.emplace(asset_load_info.last_xfb_seen, asset_load_info.asset);
+ }
+ }
+ }
+
+ if (m_memory_exceeded && total_bytes_loaded < m_max_memory_available)
+ {
+ INFO_LOG_FMT(VIDEO, "Asset memory went below limit, new assets can begin loading.");
+ m_memory_exceeded = false;
+ }
+
+ m_last_frame_total_loaded_memory = total_bytes_loaded;
+
+ if (new_pending_work.empty())
+ return;
+
+ // Now notify our workers
+ {
+ std::lock_guard guard(m_pending_work_lock);
+ std::swap(m_pending_work_per_frame, new_pending_work);
+ m_worker_thread_wake.notify_all();
+ }
+}
+
} // namespace VideoCommon
diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h
index 90d4f81a0ea2..e376b6b71f8e 100644
--- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h
+++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h
@@ -4,19 +4,24 @@
#pragma once
#include
+#include
#include