// Copyright 2023 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include #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 LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library); std::shared_ptr LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library); std::shared_ptr LoadMaterial(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library); std::shared_ptr LoadMesh(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library); private: // TODO C++20: use a 'derived_from' concept against 'CustomAsset' when available template std::shared_ptr LoadOrCreateAsset(const CustomAssetLibrary::AssetID& asset_id, std::map>& asset_map, std::shared_ptr library) { auto [it, inserted] = asset_map.try_emplace(asset_id); if (!inserted) { auto shared = it->second.lock(); if (shared) return shared; } std::shared_ptr 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> m_game_textures; std::map> m_pixel_shaders; std::map> m_materials; std::map> 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> 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> m_asset_load_thread; }; } // namespace VideoCommon