Skip to content

Commit

Permalink
Merge pull request #11877 from iwubcode/asset_library_loader
Browse files Browse the repository at this point in the history
VideoCommon: add multithreaded asset loader and define a texture asset
  • Loading branch information
AdmiralCurtiss committed Jun 2, 2023
2 parents 4c9210b + c93940c commit d03e09c
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Source/Core/DolphinLib.props
Expand Up @@ -633,8 +633,10 @@
<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\CustomTextureData.h" />
<ClInclude Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.h" />
<ClInclude Include="VideoCommon\Assets\TextureAsset.h" />
<ClInclude Include="VideoCommon\AsyncRequests.h" />
<ClInclude Include="VideoCommon\AsyncShaderCompiler.h" />
<ClInclude Include="VideoCommon\BoundingBox.h" />
Expand Down Expand Up @@ -1246,8 +1248,10 @@
<ClCompile Include="VideoCommon\AbstractTexture.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAsset.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAssetLibrary.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAssetLoader.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomTextureData.cpp" />
<ClCompile Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.cpp" />
<ClCompile Include="VideoCommon\Assets\TextureAsset.cpp" />
<ClCompile Include="VideoCommon\AsyncRequests.cpp" />
<ClCompile Include="VideoCommon\AsyncShaderCompiler.cpp" />
<ClCompile Include="VideoCommon\BoundingBox.cpp" />
Expand Down
93 changes: 93 additions & 0 deletions Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp
@@ -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 changes: 81 additions & 0 deletions Source/Core/VideoCommon/Assets/CustomAssetLoader.h
@@ -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 changes: 77 additions & 0 deletions Source/Core/VideoCommon/Assets/TextureAsset.cpp
@@ -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 changes: 32 additions & 0 deletions Source/Core/VideoCommon/Assets/TextureAsset.h
@@ -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
4 changes: 4 additions & 0 deletions Source/Core/VideoCommon/CMakeLists.txt
Expand Up @@ -12,10 +12,14 @@ add_library(videocommon
Assets/CustomAsset.h
Assets/CustomAssetLibrary.cpp
Assets/CustomAssetLibrary.h
Assets/CustomAssetLoader.cpp
Assets/CustomAssetLoader.h
Assets/CustomTextureData.cpp
Assets/CustomTextureData.h
Assets/DirectFilesystemAssetLibrary.cpp
Assets/DirectFilesystemAssetLibrary.h
Assets/TextureAsset.cpp
Assets/TextureAsset.h
AsyncRequests.cpp
AsyncRequests.h
AsyncShaderCompiler.cpp
Expand Down

0 comments on commit d03e09c

Please sign in to comment.