dolphin/Source/Core/VideoCommon/Assets/CustomAssetLoader.h

109 lines
4.3 KiB
C++

// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include "Common/Flag.h"
#include "Common/Logging/Log.h"
#include "Common/WorkQueueThread.h"
#include "VideoCommon/Assets/CustomAsset.h"
#include "VideoCommon/Assets/MaterialAsset.h"
#include "VideoCommon/Assets/MeshAsset.h"
#include "VideoCommon/Assets/ShaderAsset.h"
#include "VideoCommon/Assets/TextureAsset.h"
namespace VideoCommon
{
// This class is responsible for loading data asynchronously when requested
// and watches that data asynchronously reloading it if it changes
class CustomAssetLoader
{
public:
CustomAssetLoader() = default;
~CustomAssetLoader() = default;
CustomAssetLoader(const CustomAssetLoader&) = delete;
CustomAssetLoader(CustomAssetLoader&&) = delete;
CustomAssetLoader& operator=(const CustomAssetLoader&) = delete;
CustomAssetLoader& operator=(CustomAssetLoader&&) = delete;
void Init();
void Shutdown();
// The following Load* functions will load or create an asset associated
// with the given asset id
// Loads happen asynchronously where the data will be set now or in the future
// Callees are expected to query the underlying data with 'GetData()'
// from the 'CustomLoadableAsset' class to determine if the data is ready for use
std::shared_ptr<GameTextureAsset> LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<CustomAssetLibrary> library);
std::shared_ptr<PixelShaderAsset> LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<CustomAssetLibrary> library);
std::shared_ptr<MaterialAsset> LoadMaterial(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<CustomAssetLibrary> library);
std::shared_ptr<MeshAsset> LoadMesh(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<CustomAssetLibrary> library);
private:
// TODO C++20: use a 'derived_from' concept against 'CustomAsset' when available
template <typename AssetType>
std::shared_ptr<AssetType>
LoadOrCreateAsset(const CustomAssetLibrary::AssetID& asset_id,
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<AssetType>>& asset_map,
std::shared_ptr<CustomAssetLibrary> library)
{
auto [it, inserted] = asset_map.try_emplace(asset_id);
if (!inserted)
{
auto shared = it->second.lock();
if (shared)
return shared;
}
std::shared_ptr<AssetType> ptr(new AssetType(std::move(library), asset_id), [&](AssetType* a) {
{
std::lock_guard lk(m_asset_load_lock);
m_total_bytes_loaded -= a->GetByteSizeInMemory();
m_assets_to_monitor.erase(a->GetAssetId());
if (m_max_memory_available >= m_total_bytes_loaded && m_memory_exceeded)
{
INFO_LOG_FMT(VIDEO, "Asset memory went below limit, new assets can begin loading.");
m_memory_exceeded = false;
}
}
delete a;
});
it->second = ptr;
m_asset_load_thread.Push(it->second);
return ptr;
}
static constexpr auto TIME_BETWEEN_ASSET_MONITOR_CHECKS = std::chrono::milliseconds{500};
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<GameTextureAsset>> m_game_textures;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<PixelShaderAsset>> m_pixel_shaders;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<MaterialAsset>> m_materials;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<MeshAsset>> m_meshes;
std::thread m_asset_monitor_thread;
Common::Flag m_asset_monitor_thread_shutdown;
std::size_t m_total_bytes_loaded = 0;
std::size_t m_max_memory_available = 0;
std::atomic_bool m_memory_exceeded = false;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<CustomAsset>> m_assets_to_monitor;
// Use a recursive mutex to handle the scenario where an asset goes out of scope while
// iterating over the assets to monitor which calls the lock above in 'LoadOrCreateAsset'
std::recursive_mutex m_asset_load_lock;
Common::WorkQueueThread<std::weak_ptr<CustomAsset>> m_asset_load_thread;
};
} // namespace VideoCommon