Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11877 from iwubcode/asset_library_loader
VideoCommon: add multithreaded asset loader and define a texture asset
- Loading branch information
Showing
6 changed files
with
291 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<CustomAsset> 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<RawTextureAsset> | ||
CustomAssetLoader::LoadTexture(const CustomAssetLibrary::AssetID& asset_id, | ||
std::shared_ptr<CustomAssetLibrary> library) | ||
{ | ||
return LoadOrCreateAsset<RawTextureAsset>(asset_id, m_textures, std::move(library)); | ||
} | ||
|
||
std::shared_ptr<GameTextureAsset> | ||
CustomAssetLoader::LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id, | ||
std::shared_ptr<CustomAssetLibrary> library) | ||
{ | ||
return LoadOrCreateAsset<GameTextureAsset>(asset_id, m_game_textures, std::move(library)); | ||
} | ||
} // namespace VideoCommon |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// 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/WorkQueueThread.h" | ||
#include "VideoCommon/Assets/CustomAsset.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<RawTextureAsset> LoadTexture(const CustomAssetLibrary::AssetID& asset_id, | ||
std::shared_ptr<CustomAssetLibrary> library); | ||
|
||
std::shared_ptr<GameTextureAsset> LoadGameTexture(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) | ||
return it->second.lock(); | ||
std::shared_ptr<AssetType> ptr(new AssetType(std::move(library), asset_id), [&](AssetType* a) { | ||
asset_map.erase(a->GetAssetId()); | ||
m_total_bytes_loaded -= a->GetByteSizeInMemory(); | ||
std::lock_guard lk(m_assets_lock); | ||
m_assets_to_monitor.erase(a->GetAssetId()); | ||
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<RawTextureAsset>> m_textures; | ||
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<GameTextureAsset>> m_game_textures; | ||
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::map<CustomAssetLibrary::AssetID, std::weak_ptr<CustomAsset>> m_assets_to_monitor; | ||
std::mutex m_assets_lock; | ||
Common::WorkQueueThread<std::weak_ptr<CustomAsset>> m_asset_load_thread; | ||
}; | ||
} // namespace VideoCommon |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright 2023 Dolphin Emulator Project | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
#include "VideoCommon/Assets/TextureAsset.h" | ||
|
||
#include "Common/Logging/Log.h" | ||
|
||
namespace VideoCommon | ||
{ | ||
CustomAssetLibrary::LoadInfo RawTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) | ||
{ | ||
std::lock_guard lk(m_lock); | ||
const auto loaded_info = m_owning_library->LoadTexture(asset_id, &m_data); | ||
if (loaded_info.m_bytes_loaded == 0) | ||
return {}; | ||
m_loaded = true; | ||
return loaded_info; | ||
} | ||
|
||
CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) | ||
{ | ||
std::lock_guard lk(m_lock); | ||
const auto loaded_info = m_owning_library->LoadGameTexture(asset_id, &m_data); | ||
if (loaded_info.m_bytes_loaded == 0) | ||
return {}; | ||
m_loaded = true; | ||
return loaded_info; | ||
} | ||
|
||
bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const | ||
{ | ||
std::lock_guard lk(m_lock); | ||
|
||
if (!m_loaded) | ||
{ | ||
ERROR_LOG_FMT(VIDEO, | ||
"Game texture can't be validated for asset '{}' because it is not loaded yet.", | ||
GetAssetId()); | ||
return false; | ||
} | ||
|
||
if (m_data.m_levels.empty()) | ||
{ | ||
ERROR_LOG_FMT(VIDEO, | ||
"Game texture can't be validated for asset '{}' because no data was available.", | ||
GetAssetId()); | ||
return false; | ||
} | ||
|
||
// Verify that the aspect ratio of the texture hasn't changed, as this could have | ||
// side-effects. | ||
const VideoCommon::CustomTextureData::Level& first_mip = m_data.m_levels[0]; | ||
if (first_mip.width * native_height != first_mip.height * native_width) | ||
{ | ||
ERROR_LOG_FMT( | ||
VIDEO, | ||
"Invalid custom texture size {}x{} for game texture asset '{}'. The aspect differs " | ||
"from the native size {}x{}.", | ||
first_mip.width, first_mip.height, GetAssetId(), native_width, native_height); | ||
return false; | ||
} | ||
|
||
// Same deal if the custom texture isn't a multiple of the native size. | ||
if (native_width != 0 && native_height != 0 && | ||
(first_mip.width % native_width || first_mip.height % native_height)) | ||
{ | ||
ERROR_LOG_FMT( | ||
VIDEO, | ||
"Invalid custom texture size {}x{} for game texture asset '{}'. Please use an integer " | ||
"upscaling factor based on the native size {}x{}.", | ||
first_mip.width, first_mip.height, GetAssetId(), native_width, native_height); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} // namespace VideoCommon |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright 2023 Dolphin Emulator Project | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
#pragma once | ||
|
||
#include "VideoCommon/Assets/CustomAsset.h" | ||
#include "VideoCommon/Assets/CustomTextureData.h" | ||
|
||
namespace VideoCommon | ||
{ | ||
class RawTextureAsset final : public CustomLoadableAsset<CustomTextureData> | ||
{ | ||
public: | ||
using CustomLoadableAsset::CustomLoadableAsset; | ||
|
||
private: | ||
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; | ||
}; | ||
|
||
class GameTextureAsset final : public CustomLoadableAsset<CustomTextureData> | ||
{ | ||
public: | ||
using CustomLoadableAsset::CustomLoadableAsset; | ||
|
||
// Validates that the game texture matches the native dimensions provided | ||
// Callees are expected to call this once the data is loaded | ||
bool Validate(u32 native_width, u32 native_height) const; | ||
|
||
private: | ||
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; | ||
}; | ||
} // namespace VideoCommon |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters