mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-25 03:21:36 +02:00
VideoCommon: add resource manager and new asset loader; the resource manager uses a least recently used cache to determine which assets get priority for loading. Additionally, if the system is low on memory, assets will be purged with the less requested assets being the first to go. The loader is multithreaded now and loads assets as quickly as possible as long as memory is available
Co-authored-by: Jordan Woyak <jordan.woyak@gmail.com>
This commit is contained in:
@ -669,6 +669,8 @@
|
||||
<ClInclude Include="VideoCommon\AbstractTexture.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLoader.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomResourceManager.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomTextureData.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\MaterialAsset.h" />
|
||||
@ -1323,6 +1325,8 @@
|
||||
<ClCompile Include="VideoCommon\AbstractStagingTexture.cpp" />
|
||||
<ClCompile Include="VideoCommon\AbstractTexture.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomAssetLoader.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomResourceManager.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomTextureData.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\MaterialAsset.cpp" />
|
||||
|
157
Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
Normal file
157
Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Thread.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
void CustomAssetLoader::Initialize()
|
||||
{
|
||||
ResizeWorkerThreads(2);
|
||||
}
|
||||
|
||||
void CustomAssetLoader::Shutdown()
|
||||
{
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_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();
|
||||
}
|
||||
|
||||
void CustomAssetLoader::WorkerThreadRun(u32 thread_index)
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
auto CustomAssetLoader::TakeLoadResults() -> LoadResults
|
||||
{
|
||||
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
|
82
Source/Core/VideoCommon/Assets/CustomAssetLoader.h
Normal file
82
Source/Core/VideoCommon/Assets/CustomAssetLoader.h
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
// This class takes any number of assets
|
||||
// and loads them across a configurable
|
||||
// thread pool
|
||||
class CustomAssetLoader
|
||||
{
|
||||
public:
|
||||
CustomAssetLoader() = default;
|
||||
~CustomAssetLoader() = default;
|
||||
CustomAssetLoader(const CustomAssetLoader&) = delete;
|
||||
CustomAssetLoader(CustomAssetLoader&&) = delete;
|
||||
CustomAssetLoader& operator=(const CustomAssetLoader&) = delete;
|
||||
CustomAssetLoader& operator=(CustomAssetLoader&&) = delete;
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
using AssetHandle = std::pair<std::size_t, bool>;
|
||||
struct LoadResults
|
||||
|
||||
{
|
||||
std::vector<AssetHandle> asset_handles;
|
||||
s64 change_in_memory;
|
||||
};
|
||||
|
||||
// Returns a vector of loaded asset handle / loaded result pairs
|
||||
// and the change in memory.
|
||||
LoadResults TakeLoadResults();
|
||||
|
||||
// Schedule assets to load on the worker threads
|
||||
// and set how much memory is available for loading these additional assets.
|
||||
void ScheduleAssetsToLoad(std::list<CustomAsset*> assets_to_load, u64 allowed_memory);
|
||||
|
||||
void Reset(bool restart_worker_threads = true);
|
||||
|
||||
private:
|
||||
bool StartWorkerThreads(u32 num_worker_threads);
|
||||
bool ResizeWorkerThreads(u32 num_worker_threads);
|
||||
bool HasWorkerThreads() const;
|
||||
void StopWorkerThreads();
|
||||
|
||||
void WorkerThreadRun(u32 thread_index);
|
||||
|
||||
Common::Flag m_exit_flag;
|
||||
|
||||
std::vector<std::thread> m_worker_threads;
|
||||
|
||||
std::mutex m_assets_to_load_lock;
|
||||
std::list<CustomAsset*> m_assets_to_load;
|
||||
|
||||
std::condition_variable m_worker_thread_wake;
|
||||
|
||||
std::vector<AssetHandle> m_asset_handles_loaded;
|
||||
|
||||
// Memory available to load new assets.
|
||||
s64 m_allowed_memory = 0;
|
||||
|
||||
// Change in memory from just-loaded/unloaded asset results yet to be taken by the Manager.
|
||||
std::atomic<s64> m_change_in_memory = 0;
|
||||
|
||||
std::mutex m_assets_loaded_lock;
|
||||
|
||||
std::set<std::size_t> m_handles_in_progress;
|
||||
};
|
||||
} // namespace VideoCommon
|
229
Source/Core/VideoCommon/Assets/CustomResourceManager.cpp
Normal file
229
Source/Core/VideoCommon/Assets/CustomResourceManager.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MemoryUtil.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
void CustomResourceManager::Initialize()
|
||||
{
|
||||
// Use half of available system memory but leave at least 2GiB unused for system stability.
|
||||
constexpr size_t must_keep_unused = 2 * size_t(1024 * 1024 * 1024);
|
||||
|
||||
const size_t sys_mem = Common::MemPhysical();
|
||||
const size_t keep_unused_mem = std::max(sys_mem / 2, std::min(sys_mem, must_keep_unused));
|
||||
|
||||
m_max_ram_available = sys_mem - keep_unused_mem;
|
||||
|
||||
if (m_max_ram_available == 0)
|
||||
ERROR_LOG_FMT(VIDEO, "Not enough system memory for custom resources.");
|
||||
|
||||
m_asset_loader.Initialize();
|
||||
|
||||
m_xfb_event =
|
||||
AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(); }, "CustomResourceManager");
|
||||
}
|
||||
|
||||
void CustomResourceManager::Shutdown()
|
||||
{
|
||||
Reset();
|
||||
|
||||
m_asset_loader.Shutdown();
|
||||
}
|
||||
|
||||
void CustomResourceManager::Reset()
|
||||
{
|
||||
m_asset_loader.Reset(true);
|
||||
|
||||
m_active_assets = {};
|
||||
m_pending_assets = {};
|
||||
m_asset_handle_to_data.clear();
|
||||
m_asset_id_to_handle.clear();
|
||||
m_texture_data_asset_cache.clear();
|
||||
m_dirty_assets.clear();
|
||||
m_ram_used = 0;
|
||||
}
|
||||
|
||||
void CustomResourceManager::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id)
|
||||
{
|
||||
std::lock_guard guard(m_dirty_mutex);
|
||||
m_dirty_assets.insert(asset_id);
|
||||
}
|
||||
|
||||
CustomResourceManager::TextureTimePair CustomResourceManager::GetTextureDataFromAsset(
|
||||
const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
auto& resource = m_texture_data_asset_cache[asset_id];
|
||||
if (resource.asset_data != nullptr &&
|
||||
resource.asset_data->load_status == AssetData::LoadStatus::ResourceDataAvailable)
|
||||
{
|
||||
m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset);
|
||||
return {resource.texture_data, resource.asset->GetLastLoadedTime()};
|
||||
}
|
||||
|
||||
// If there is an error, don't try and load again until the error is fixed
|
||||
if (resource.asset_data != nullptr && resource.asset_data->has_load_error)
|
||||
return {};
|
||||
|
||||
LoadTextureDataAsset(asset_id, std::move(library), &resource);
|
||||
m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void CustomResourceManager::LoadTextureDataAsset(
|
||||
const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library, InternalTextureDataResource* resource)
|
||||
{
|
||||
if (!resource->asset)
|
||||
{
|
||||
resource->asset =
|
||||
CreateAsset<TextureAsset>(asset_id, AssetData::AssetType::TextureData, std::move(library));
|
||||
resource->asset_data = &m_asset_handle_to_data[resource->asset->GetHandle()];
|
||||
}
|
||||
|
||||
auto texture_data = resource->asset->GetData();
|
||||
if (!texture_data || resource->asset_data->load_status == AssetData::LoadStatus::PendingReload)
|
||||
{
|
||||
// Tell the system we are still interested in loading this asset
|
||||
const auto asset_handle = resource->asset->GetHandle();
|
||||
m_pending_assets.MakeAssetHighestPriority(asset_handle,
|
||||
m_asset_handle_to_data[asset_handle].asset.get());
|
||||
}
|
||||
else if (resource->asset_data->load_status == AssetData::LoadStatus::LoadFinished)
|
||||
{
|
||||
resource->texture_data = std::move(texture_data);
|
||||
resource->asset_data->load_status = AssetData::LoadStatus::ResourceDataAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::XFBTriggered()
|
||||
{
|
||||
ProcessDirtyAssets();
|
||||
ProcessLoadedAssets();
|
||||
|
||||
if (m_ram_used > m_max_ram_available)
|
||||
{
|
||||
RemoveAssetsUntilBelowMemoryLimit();
|
||||
}
|
||||
|
||||
if (m_pending_assets.IsEmpty())
|
||||
return;
|
||||
|
||||
if (m_ram_used > m_max_ram_available)
|
||||
return;
|
||||
|
||||
const u64 allowed_memory = m_max_ram_available - m_ram_used;
|
||||
m_asset_loader.ScheduleAssetsToLoad(m_pending_assets.Elements(), allowed_memory);
|
||||
}
|
||||
|
||||
void CustomResourceManager::ProcessDirtyAssets()
|
||||
{
|
||||
decltype(m_dirty_assets) dirty_assets;
|
||||
|
||||
if (const auto lk = std::unique_lock{m_dirty_mutex, std::try_to_lock})
|
||||
std::swap(dirty_assets, m_dirty_assets);
|
||||
|
||||
const auto now = CustomAsset::ClockType::now();
|
||||
for (const auto& asset_id : dirty_assets)
|
||||
{
|
||||
if (const auto it = m_asset_id_to_handle.find(asset_id); it != m_asset_id_to_handle.end())
|
||||
{
|
||||
const auto asset_handle = it->second;
|
||||
AssetData& asset_data = m_asset_handle_to_data[asset_handle];
|
||||
asset_data.load_status = AssetData::LoadStatus::PendingReload;
|
||||
asset_data.load_request_time = now;
|
||||
|
||||
// Asset was reloaded, clear any errors we might have
|
||||
asset_data.has_load_error = false;
|
||||
|
||||
m_pending_assets.InsertAsset(it->second, asset_data.asset.get());
|
||||
|
||||
DEBUG_LOG_FMT(VIDEO, "Dirty asset pending reload: {}", asset_data.asset->GetAssetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::ProcessLoadedAssets()
|
||||
{
|
||||
const auto load_results = m_asset_loader.TakeLoadResults();
|
||||
|
||||
// Update the ram with the change in memory from the loader
|
||||
//
|
||||
// Note: Assets with outstanding reload requests will have
|
||||
// two copies in memory temporarily (the old data stored in
|
||||
// the asset shared_ptr that the resource manager owns, and
|
||||
// the new data loaded from the loader in the asset's shared_ptr)
|
||||
// This temporary duplication will not be reflected in the
|
||||
// resource manager's ram used
|
||||
m_ram_used += load_results.change_in_memory;
|
||||
|
||||
for (const auto& [handle, load_successful] : load_results.asset_handles)
|
||||
{
|
||||
AssetData& asset_data = m_asset_handle_to_data[handle];
|
||||
|
||||
// If we have a reload request that is newer than our loaded time
|
||||
// we need to wait for another reload.
|
||||
if (asset_data.load_request_time > asset_data.asset->GetLastLoadedTime())
|
||||
continue;
|
||||
|
||||
m_pending_assets.RemoveAsset(handle);
|
||||
|
||||
asset_data.load_request_time = {};
|
||||
if (!load_successful)
|
||||
{
|
||||
asset_data.has_load_error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_active_assets.InsertAsset(handle, asset_data.asset.get());
|
||||
asset_data.load_status = AssetData::LoadStatus::LoadFinished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit()
|
||||
{
|
||||
const u64 threshold_ram = m_max_ram_available * 8 / 10;
|
||||
|
||||
if (m_ram_used > threshold_ram)
|
||||
{
|
||||
INFO_LOG_FMT(VIDEO, "Memory usage over threshold: {}", UICommon::FormatSize(m_ram_used));
|
||||
}
|
||||
|
||||
// Clear out least recently used resources until
|
||||
// we get safely in our threshold
|
||||
while (m_ram_used > threshold_ram && m_active_assets.Size() > 0)
|
||||
{
|
||||
auto* const asset = m_active_assets.RemoveLowestPriorityAsset();
|
||||
|
||||
AssetData& asset_data = m_asset_handle_to_data[asset->GetHandle()];
|
||||
|
||||
// Remove the resource manager's cached entry with its asset data
|
||||
if (asset_data.type == AssetData::AssetType::TextureData)
|
||||
{
|
||||
m_texture_data_asset_cache.erase(asset->GetAssetId());
|
||||
}
|
||||
// Remove the asset's copy
|
||||
const std::size_t bytes_unloaded = asset_data.asset->Unload();
|
||||
m_ram_used -= bytes_unloaded;
|
||||
|
||||
asset_data.load_status = AssetData::LoadStatus::Unloaded;
|
||||
asset_data.load_request_time = {};
|
||||
|
||||
INFO_LOG_FMT(VIDEO, "Unloading asset: {} ({})", asset_data.asset->GetAssetId(),
|
||||
UICommon::FormatSize(bytes_unloaded));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
215
Source/Core/VideoCommon/Assets/CustomResourceManager.h
Normal file
215
Source/Core/VideoCommon/Assets/CustomResourceManager.h
Normal file
@ -0,0 +1,215 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/HookableEvent.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class TextureAsset;
|
||||
|
||||
// The resource manager manages custom resources (textures, shaders, meshes)
|
||||
// called assets. These assets are loaded using a priority system,
|
||||
// where assets requested more often gets loaded first. This system
|
||||
// also tracks memory usage and if memory usage goes over a calculated limit,
|
||||
// then assets will be purged with older assets being targeted first.
|
||||
class CustomResourceManager
|
||||
{
|
||||
public:
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void Reset();
|
||||
|
||||
// Request that an asset be reloaded
|
||||
void MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id);
|
||||
|
||||
void XFBTriggered();
|
||||
|
||||
using TextureTimePair = std::pair<std::shared_ptr<CustomTextureData>, CustomAsset::TimeType>;
|
||||
|
||||
// Returns a pair with the custom texture data and the time it was last loaded
|
||||
// Callees are not expected to hold onto the shared_ptr as that will prevent
|
||||
// the resource manager from being able to properly release data
|
||||
TextureTimePair GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||
|
||||
private:
|
||||
// A generic interface to describe an assets' type
|
||||
// and load state
|
||||
struct AssetData
|
||||
{
|
||||
std::unique_ptr<CustomAsset> asset;
|
||||
CustomAsset::TimeType load_request_time = {};
|
||||
bool has_load_error = false;
|
||||
|
||||
enum class AssetType
|
||||
{
|
||||
TextureData
|
||||
};
|
||||
AssetType type;
|
||||
|
||||
enum class LoadStatus
|
||||
{
|
||||
PendingReload,
|
||||
LoadFinished,
|
||||
ResourceDataAvailable,
|
||||
Unloaded,
|
||||
};
|
||||
LoadStatus load_status = LoadStatus::PendingReload;
|
||||
};
|
||||
|
||||
// A structure to represent some raw texture data
|
||||
// (this data hasn't hit the GPU yet, used for custom textures)
|
||||
struct InternalTextureDataResource
|
||||
{
|
||||
AssetData* asset_data = nullptr;
|
||||
VideoCommon::TextureAsset* asset = nullptr;
|
||||
std::shared_ptr<CustomTextureData> texture_data;
|
||||
};
|
||||
|
||||
void LoadTextureDataAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
InternalTextureDataResource* resource);
|
||||
|
||||
void ProcessDirtyAssets();
|
||||
void ProcessLoadedAssets();
|
||||
void RemoveAssetsUntilBelowMemoryLimit();
|
||||
|
||||
template <typename T>
|
||||
T* CreateAsset(const CustomAssetLibrary::AssetID& asset_id, AssetData::AssetType asset_type,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
const auto [it, added] =
|
||||
m_asset_id_to_handle.try_emplace(asset_id, m_asset_handle_to_data.size());
|
||||
|
||||
if (added)
|
||||
{
|
||||
AssetData asset_data;
|
||||
asset_data.asset = std::make_unique<T>(library, asset_id, it->second);
|
||||
asset_data.type = asset_type;
|
||||
asset_data.load_request_time = {};
|
||||
asset_data.has_load_error = false;
|
||||
|
||||
m_asset_handle_to_data.insert_or_assign(it->second, std::move(asset_data));
|
||||
}
|
||||
auto& asset_data_from_handle = m_asset_handle_to_data[it->second];
|
||||
asset_data_from_handle.load_status = AssetData::LoadStatus::PendingReload;
|
||||
|
||||
return static_cast<T*>(asset_data_from_handle.asset.get());
|
||||
}
|
||||
|
||||
// Maintains a priority-sorted list of assets.
|
||||
// Used to figure out which assets to load or unload first.
|
||||
// Most recently used assets get marked with highest priority.
|
||||
class AssetPriorityQueue
|
||||
{
|
||||
public:
|
||||
const auto& Elements() const { return m_assets; }
|
||||
|
||||
// Inserts or moves the asset to the top of the queue.
|
||||
void MakeAssetHighestPriority(u64 asset_handle, CustomAsset* asset)
|
||||
{
|
||||
RemoveAsset(asset_handle);
|
||||
m_assets.push_front(asset);
|
||||
|
||||
// See CreateAsset for how a handle gets defined
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
m_iterator_lookup.resize(asset_handle + 1, m_assets.end());
|
||||
|
||||
m_iterator_lookup[asset_handle] = m_assets.begin();
|
||||
}
|
||||
|
||||
// Inserts an asset at lowest priority or
|
||||
// does nothing if asset is already in the queue.
|
||||
void InsertAsset(u64 asset_handle, CustomAsset* asset)
|
||||
{
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
m_iterator_lookup.resize(asset_handle + 1, m_assets.end());
|
||||
|
||||
if (m_iterator_lookup[asset_handle] == m_assets.end())
|
||||
{
|
||||
m_assets.push_back(asset);
|
||||
m_iterator_lookup[asset_handle] = std::prev(m_assets.end());
|
||||
}
|
||||
}
|
||||
|
||||
CustomAsset* RemoveLowestPriorityAsset()
|
||||
{
|
||||
if (m_assets.empty()) [[unlikely]]
|
||||
return nullptr;
|
||||
auto* const ret = m_assets.back();
|
||||
if (ret != nullptr)
|
||||
{
|
||||
m_iterator_lookup[ret->GetHandle()] = m_assets.end();
|
||||
}
|
||||
m_assets.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RemoveAsset(u64 asset_handle)
|
||||
{
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
return;
|
||||
|
||||
const auto iter = m_iterator_lookup[asset_handle];
|
||||
if (iter != m_assets.end())
|
||||
{
|
||||
m_assets.erase(iter);
|
||||
m_iterator_lookup[asset_handle] = m_assets.end();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return m_assets.empty(); }
|
||||
|
||||
std::size_t Size() const { return m_assets.size(); }
|
||||
|
||||
private:
|
||||
std::list<CustomAsset*> m_assets;
|
||||
|
||||
// Handle-to-iterator lookup for fast access.
|
||||
// Grows as needed on insert.
|
||||
std::vector<decltype(m_assets)::iterator> m_iterator_lookup;
|
||||
};
|
||||
|
||||
// Assets that are currently active in memory, in order of most recently used by the game.
|
||||
AssetPriorityQueue m_active_assets;
|
||||
|
||||
// Assets that need to be loaded.
|
||||
// e.g. Because the game tried to use them or because they changed on disk.
|
||||
// Ordered by most recently used.
|
||||
AssetPriorityQueue m_pending_assets;
|
||||
|
||||
std::map<std::size_t, AssetData> m_asset_handle_to_data;
|
||||
std::map<CustomAssetLibrary::AssetID, std::size_t> m_asset_id_to_handle;
|
||||
|
||||
// Memory used by currently "loaded" assets.
|
||||
u64 m_ram_used = 0;
|
||||
|
||||
// A calculated amount of memory to avoid exceeding.
|
||||
u64 m_max_ram_available = 0;
|
||||
|
||||
std::map<CustomAssetLibrary::AssetID, InternalTextureDataResource> m_texture_data_asset_cache;
|
||||
|
||||
std::mutex m_dirty_mutex;
|
||||
std::set<CustomAssetLibrary::AssetID> m_dirty_assets;
|
||||
|
||||
CustomAssetLoader m_asset_loader;
|
||||
|
||||
Common::EventHook m_xfb_event;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
@ -13,6 +13,8 @@ add_library(videocommon
|
||||
Assets/CustomAssetLibrary.h
|
||||
Assets/CustomAssetLoader.cpp
|
||||
Assets/CustomAssetLoader.h
|
||||
Assets/CustomResourceManager.cpp
|
||||
Assets/CustomResourceManager.h
|
||||
Assets/CustomTextureData.cpp
|
||||
Assets/CustomTextureData.h
|
||||
Assets/DirectFilesystemAssetLibrary.cpp
|
||||
|
Reference in New Issue
Block a user