mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-25 07:21:14 +01:00
Merge pull request #11877 from iwubcode/asset_library_loader
VideoCommon: add multithreaded asset loader and define a texture asset
This commit is contained in:
commit
d03e09c8fd
@ -633,8 +633,10 @@
|
|||||||
<ClInclude Include="VideoCommon\AbstractTexture.h" />
|
<ClInclude Include="VideoCommon\AbstractTexture.h" />
|
||||||
<ClInclude Include="VideoCommon\Assets\CustomAsset.h" />
|
<ClInclude Include="VideoCommon\Assets\CustomAsset.h" />
|
||||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLibrary.h" />
|
<ClInclude Include="VideoCommon\Assets\CustomAssetLibrary.h" />
|
||||||
|
<ClInclude Include="VideoCommon\Assets\CustomAssetLoader.h" />
|
||||||
<ClInclude Include="VideoCommon\Assets\CustomTextureData.h" />
|
<ClInclude Include="VideoCommon\Assets\CustomTextureData.h" />
|
||||||
<ClInclude Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.h" />
|
<ClInclude Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.h" />
|
||||||
|
<ClInclude Include="VideoCommon\Assets\TextureAsset.h" />
|
||||||
<ClInclude Include="VideoCommon\AsyncRequests.h" />
|
<ClInclude Include="VideoCommon\AsyncRequests.h" />
|
||||||
<ClInclude Include="VideoCommon\AsyncShaderCompiler.h" />
|
<ClInclude Include="VideoCommon\AsyncShaderCompiler.h" />
|
||||||
<ClInclude Include="VideoCommon\BoundingBox.h" />
|
<ClInclude Include="VideoCommon\BoundingBox.h" />
|
||||||
@ -1246,8 +1248,10 @@
|
|||||||
<ClCompile Include="VideoCommon\AbstractTexture.cpp" />
|
<ClCompile Include="VideoCommon\AbstractTexture.cpp" />
|
||||||
<ClCompile Include="VideoCommon\Assets\CustomAsset.cpp" />
|
<ClCompile Include="VideoCommon\Assets\CustomAsset.cpp" />
|
||||||
<ClCompile Include="VideoCommon\Assets\CustomAssetLibrary.cpp" />
|
<ClCompile Include="VideoCommon\Assets\CustomAssetLibrary.cpp" />
|
||||||
|
<ClCompile Include="VideoCommon\Assets\CustomAssetLoader.cpp" />
|
||||||
<ClCompile Include="VideoCommon\Assets\CustomTextureData.cpp" />
|
<ClCompile Include="VideoCommon\Assets\CustomTextureData.cpp" />
|
||||||
<ClCompile Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.cpp" />
|
<ClCompile Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.cpp" />
|
||||||
|
<ClCompile Include="VideoCommon\Assets\TextureAsset.cpp" />
|
||||||
<ClCompile Include="VideoCommon\AsyncRequests.cpp" />
|
<ClCompile Include="VideoCommon\AsyncRequests.cpp" />
|
||||||
<ClCompile Include="VideoCommon\AsyncShaderCompiler.cpp" />
|
<ClCompile Include="VideoCommon\AsyncShaderCompiler.cpp" />
|
||||||
<ClCompile Include="VideoCommon\BoundingBox.cpp" />
|
<ClCompile Include="VideoCommon\BoundingBox.cpp" />
|
||||||
|
93
Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
Normal file
93
Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
Normal file
@ -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
|
81
Source/Core/VideoCommon/Assets/CustomAssetLoader.h
Normal file
81
Source/Core/VideoCommon/Assets/CustomAssetLoader.h
Normal file
@ -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
|
77
Source/Core/VideoCommon/Assets/TextureAsset.cpp
Normal file
77
Source/Core/VideoCommon/Assets/TextureAsset.cpp
Normal file
@ -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
|
32
Source/Core/VideoCommon/Assets/TextureAsset.h
Normal file
32
Source/Core/VideoCommon/Assets/TextureAsset.h
Normal file
@ -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
|
@ -12,10 +12,14 @@ add_library(videocommon
|
|||||||
Assets/CustomAsset.h
|
Assets/CustomAsset.h
|
||||||
Assets/CustomAssetLibrary.cpp
|
Assets/CustomAssetLibrary.cpp
|
||||||
Assets/CustomAssetLibrary.h
|
Assets/CustomAssetLibrary.h
|
||||||
|
Assets/CustomAssetLoader.cpp
|
||||||
|
Assets/CustomAssetLoader.h
|
||||||
Assets/CustomTextureData.cpp
|
Assets/CustomTextureData.cpp
|
||||||
Assets/CustomTextureData.h
|
Assets/CustomTextureData.h
|
||||||
Assets/DirectFilesystemAssetLibrary.cpp
|
Assets/DirectFilesystemAssetLibrary.cpp
|
||||||
Assets/DirectFilesystemAssetLibrary.h
|
Assets/DirectFilesystemAssetLibrary.h
|
||||||
|
Assets/TextureAsset.cpp
|
||||||
|
Assets/TextureAsset.h
|
||||||
AsyncRequests.cpp
|
AsyncRequests.cpp
|
||||||
AsyncRequests.h
|
AsyncRequests.h
|
||||||
AsyncShaderCompiler.cpp
|
AsyncShaderCompiler.cpp
|
||||||
|
Loading…
x
Reference in New Issue
Block a user