diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index 5ae7b6fd67..05142ae672 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -633,8 +633,10 @@
+
+
@@ -1246,8 +1248,10 @@
+
+
diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
new file mode 100644
index 0000000000..d2189e655b
--- /dev/null
+++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
@@ -0,0 +1,93 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/Assets/CustomAssetLoader.h"
+
+#include "Common/Logging/Log.h"
+#include "Common/MemoryUtil.h"
+#include "VideoCommon/Assets/CustomAssetLibrary.h"
+
+namespace VideoCommon
+{
+void CustomAssetLoader::Init()
+{
+ m_asset_monitor_thread_shutdown.Clear();
+
+ 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);
+
+ 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_assets_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 (ptr->Load())
+ {
+ if (m_max_memory_available >= m_total_bytes_loaded + ptr->GetByteSizeInMemory())
+ {
+ m_total_bytes_loaded += ptr->GetByteSizeInMemory();
+
+ std::lock_guard lk(m_assets_lock);
+ m_assets_to_monitor.try_emplace(ptr->GetAssetId(), ptr);
+ }
+ else
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load asset {} because there was not enough memory.",
+ ptr->GetAssetId());
+ }
+ }
+ }
+ });
+}
+
+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;
+}
+
+std::shared_ptr
+CustomAssetLoader::LoadTexture(const CustomAssetLibrary::AssetID& asset_id,
+ std::shared_ptr library)
+{
+ return LoadOrCreateAsset(asset_id, m_textures, std::move(library));
+}
+
+std::shared_ptr
+CustomAssetLoader::LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id,
+ std::shared_ptr library)
+{
+ return LoadOrCreateAsset(asset_id, m_game_textures, std::move(library));
+}
+} // namespace VideoCommon
diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h
new file mode 100644
index 0000000000..da1f54b133
--- /dev/null
+++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h
@@ -0,0 +1,81 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+#include