mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #13386 from iwubcode/resource_manager_system
VideoCommon: add resource manager, tracks resources to load assets in optimal way and manage memory
This commit is contained in:
@@ -84,6 +84,9 @@
|
||||
[submodule "Externals/Vulkan-Headers"]
|
||||
path = Externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "Externals/watcher/watcher"]
|
||||
path = Externals/watcher/watcher
|
||||
url = https://github.com/e-dant/watcher.git
|
||||
[submodule "Externals/SFML/SFML"]
|
||||
path = Externals/SFML/SFML
|
||||
url = https://github.com/SFML/SFML.git
|
||||
|
||||
@@ -784,6 +784,8 @@ if (USE_RETRO_ACHIEVEMENTS)
|
||||
add_subdirectory(Externals/rcheevos)
|
||||
endif()
|
||||
|
||||
add_subdirectory(Externals/watcher)
|
||||
|
||||
########################################
|
||||
# Pre-build events: Define configuration variables and write SCM info header
|
||||
#
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
add_library(watcher INTERFACE IMPORTED GLOBAL)
|
||||
set_target_properties(watcher PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/watcher/include
|
||||
)
|
||||
+1
Submodule Externals/watcher/watcher added at b03bdcfc11
@@ -64,6 +64,8 @@ add_library(common
|
||||
FatFsUtil.h
|
||||
FileSearch.cpp
|
||||
FileSearch.h
|
||||
FilesystemWatcher.cpp
|
||||
FilesystemWatcher.h
|
||||
FileUtil.cpp
|
||||
FileUtil.h
|
||||
FixedSizeQueue.h
|
||||
@@ -184,6 +186,7 @@ PRIVATE
|
||||
FatFs
|
||||
Iconv::Iconv
|
||||
spng::spng
|
||||
watcher
|
||||
${VTUNE_LIBRARIES}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/FilesystemWatcher.h"
|
||||
|
||||
#include <wtr/watcher.hpp>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
FilesystemWatcher::FilesystemWatcher() = default;
|
||||
FilesystemWatcher::~FilesystemWatcher() = default;
|
||||
|
||||
void FilesystemWatcher::Watch(const std::string& path)
|
||||
{
|
||||
const auto [iter, inserted] = m_watched_paths.try_emplace(path, nullptr);
|
||||
if (inserted)
|
||||
{
|
||||
iter->second = std::make_unique<wtr::watch>(path, [this](wtr::event e) {
|
||||
const auto watched_path = PathToString(e.path_name);
|
||||
if (e.path_type == wtr::event::path_type::watcher)
|
||||
{
|
||||
if (watched_path.starts_with('e'))
|
||||
ERROR_LOG_FMT(COMMON, "Filesystem watcher: '{}'", watched_path);
|
||||
else if (watched_path.starts_with('w'))
|
||||
WARN_LOG_FMT(COMMON, "Filesystem watcher: '{}'", watched_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.effect_type == wtr::event::effect_type::create)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(watched_path);
|
||||
PathAdded(path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::modify)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(watched_path);
|
||||
PathModified(path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::rename)
|
||||
{
|
||||
if (!e.associated)
|
||||
{
|
||||
WARN_LOG_FMT(COMMON, "Rename on path '{}' seen without association!", watched_path);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old_path = WithUnifiedPathSeparators(watched_path);
|
||||
const auto new_path = WithUnifiedPathSeparators(PathToString(e.associated->path_name));
|
||||
PathRenamed(old_path, new_path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::destroy)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(watched_path);
|
||||
PathDeleted(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FilesystemWatcher::Unwatch(const std::string& path)
|
||||
{
|
||||
m_watched_paths.erase(path);
|
||||
}
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace wtr
|
||||
{
|
||||
inline namespace watcher
|
||||
{
|
||||
class watch;
|
||||
}
|
||||
} // namespace wtr
|
||||
|
||||
namespace Common
|
||||
{
|
||||
// A class that can watch a path and receive callbacks
|
||||
// when files or directories underneath that path receive events
|
||||
class FilesystemWatcher
|
||||
{
|
||||
public:
|
||||
FilesystemWatcher();
|
||||
virtual ~FilesystemWatcher();
|
||||
|
||||
void Watch(const std::string& path);
|
||||
void Unwatch(const std::string& path);
|
||||
|
||||
private:
|
||||
// A new file or folder was added to one of the watched paths
|
||||
virtual void PathAdded(std::string_view path) {}
|
||||
|
||||
// A file or folder was modified in one of the watched paths
|
||||
virtual void PathModified(std::string_view path) {}
|
||||
|
||||
// A file or folder was renamed in one of the watched paths
|
||||
virtual void PathRenamed(std::string_view old_path, std::string_view new_path) {}
|
||||
|
||||
// A file or folder was deleted in one of the watched paths
|
||||
virtual void PathDeleted(std::string_view path) {}
|
||||
|
||||
std::map<std::string, std::unique_ptr<wtr::watch>> m_watched_paths;
|
||||
};
|
||||
} // namespace Common
|
||||
@@ -82,7 +82,6 @@
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
#include "InputCommon/GCAdapter.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/AsyncRequests.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
#include "VideoCommon/FrameDumper.h"
|
||||
@@ -528,9 +527,6 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
|
||||
|
||||
FreeLook::LoadInputConfig();
|
||||
|
||||
system.GetCustomAssetLoader().Init();
|
||||
Common::ScopeGuard asset_loader_guard([&system] { system.GetCustomAssetLoader().Shutdown(); });
|
||||
|
||||
system.GetMovie().Init(*boot);
|
||||
Common::ScopeGuard movie_guard([&system] { system.GetMovie().Shutdown(); });
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include "IOS/USB/Emulated/Infinity.h"
|
||||
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||
#include "IOS/USB/USBScanner.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
@@ -96,7 +96,7 @@ struct System::Impl
|
||||
VideoInterface::VideoInterfaceManager m_video_interface;
|
||||
Interpreter m_interpreter;
|
||||
JitInterface m_jit_interface;
|
||||
VideoCommon::CustomAssetLoader m_custom_asset_loader;
|
||||
VideoCommon::CustomResourceManager m_custom_resource_manager;
|
||||
FifoPlayer m_fifo_player;
|
||||
FifoRecorder m_fifo_recorder;
|
||||
Movie::MovieManager m_movie;
|
||||
@@ -335,8 +335,8 @@ VideoInterface::VideoInterfaceManager& System::GetVideoInterface() const
|
||||
return m_impl->m_video_interface;
|
||||
}
|
||||
|
||||
VideoCommon::CustomAssetLoader& System::GetCustomAssetLoader() const
|
||||
VideoCommon::CustomResourceManager& System::GetCustomResourceManager() const
|
||||
{
|
||||
return m_impl->m_custom_asset_loader;
|
||||
return m_impl->m_custom_resource_manager;
|
||||
}
|
||||
} // namespace Core
|
||||
|
||||
@@ -108,8 +108,8 @@ class SystemTimersManager;
|
||||
}
|
||||
namespace VideoCommon
|
||||
{
|
||||
class CustomAssetLoader;
|
||||
}
|
||||
class CustomResourceManager;
|
||||
} // namespace VideoCommon
|
||||
namespace VideoInterface
|
||||
{
|
||||
class VideoInterfaceManager;
|
||||
@@ -197,7 +197,7 @@ public:
|
||||
VertexShaderManager& GetVertexShaderManager() const;
|
||||
XFStateManager& GetXFStateManager() const;
|
||||
VideoInterface::VideoInterfaceManager& GetVideoInterface() const;
|
||||
VideoCommon::CustomAssetLoader& GetCustomAssetLoader() const;
|
||||
VideoCommon::CustomResourceManager& GetCustomResourceManager() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<ClInclude Include="Common\Event.h" />
|
||||
<ClInclude Include="Common\FatFsUtil.h" />
|
||||
<ClInclude Include="Common\FileSearch.h" />
|
||||
<ClInclude Include="Common\FilesystemWatcher.h" />
|
||||
<ClInclude Include="Common\FileUtil.h" />
|
||||
<ClInclude Include="Common\FixedSizeQueue.h" />
|
||||
<ClInclude Include="Common\Flag.h" />
|
||||
@@ -669,12 +670,16 @@
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLoader.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomResourceManager.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomTextureData.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\MaterialAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\MeshAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\ShaderAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\TextureAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\TextureAssetUtils.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\Types.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\WatchableFilesystemAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\AsyncRequests.h" />
|
||||
<ClInclude Include="VideoCommon\AsyncShaderCompiler.h" />
|
||||
<ClInclude Include="VideoCommon\BoundingBox.h" />
|
||||
@@ -814,6 +819,7 @@
|
||||
<ClCompile Include="Common\ENet.cpp" />
|
||||
<ClCompile Include="Common\FatFsUtil.cpp" />
|
||||
<ClCompile Include="Common\FileSearch.cpp" />
|
||||
<ClCompile Include="Common\FilesystemWatcher.cpp" />
|
||||
<ClCompile Include="Common\FileUtil.cpp" />
|
||||
<ClCompile Include="Common\FloatUtils.cpp" />
|
||||
<ClCompile Include="Common\GekkoDisassembler.cpp" />
|
||||
@@ -1320,14 +1326,15 @@
|
||||
<ClCompile Include="VideoCommon\AbstractStagingTexture.cpp" />
|
||||
<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\CustomResourceManager.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomTextureData.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\MaterialAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\MeshAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\ShaderAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\TextureAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\TextureAssetUtils.cpp" />
|
||||
<ClCompile Include="VideoCommon\AsyncRequests.cpp" />
|
||||
<ClCompile Include="VideoCommon\AsyncShaderCompiler.cpp" />
|
||||
<ClCompile Include="VideoCommon\BoundingBox.cpp" />
|
||||
|
||||
@@ -3,46 +3,54 @@
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
CustomAsset::CustomAsset(std::shared_ptr<CustomAssetLibrary> library,
|
||||
const CustomAssetLibrary::AssetID& asset_id)
|
||||
: m_owning_library(std::move(library)), m_asset_id(asset_id)
|
||||
const CustomAssetLibrary::AssetID& asset_id, u64 asset_handle)
|
||||
: m_owning_library(std::move(library)), m_asset_id(asset_id), m_handle(asset_handle)
|
||||
{
|
||||
}
|
||||
|
||||
bool CustomAsset::Load()
|
||||
{
|
||||
const auto load_information = LoadImpl(m_asset_id);
|
||||
if (load_information.m_bytes_loaded > 0)
|
||||
{
|
||||
std::lock_guard lk(m_info_lock);
|
||||
m_bytes_loaded = load_information.m_bytes_loaded;
|
||||
m_last_loaded_time = load_information.m_load_time;
|
||||
}
|
||||
return load_information.m_bytes_loaded != 0;
|
||||
}
|
||||
|
||||
CustomAssetLibrary::TimeType CustomAsset::GetLastWriteTime() const
|
||||
{
|
||||
return m_owning_library->GetLastAssetWriteTime(m_asset_id);
|
||||
}
|
||||
|
||||
const CustomAssetLibrary::TimeType& CustomAsset::GetLastLoadedTime() const
|
||||
std::size_t CustomAsset::Load()
|
||||
{
|
||||
std::lock_guard lk(m_info_lock);
|
||||
// The load time needs to come from before the data is actually read.
|
||||
// Using a time point from after the read marks the asset as more up-to-date than it actually is,
|
||||
// and has potential to race (and not be updated) if a change happens immediately after load.
|
||||
const auto load_time = ClockType::now();
|
||||
|
||||
const auto load_information = LoadImpl(m_asset_id);
|
||||
if (load_information.bytes_loaded > 0)
|
||||
{
|
||||
m_bytes_loaded = load_information.bytes_loaded;
|
||||
m_last_loaded_time = load_time;
|
||||
return m_bytes_loaded;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t CustomAsset::Unload()
|
||||
{
|
||||
std::lock_guard lk(m_info_lock);
|
||||
UnloadImpl();
|
||||
return std::exchange(m_bytes_loaded, 0);
|
||||
}
|
||||
|
||||
CustomAsset::TimeType CustomAsset::GetLastLoadedTime() const
|
||||
{
|
||||
return m_last_loaded_time;
|
||||
}
|
||||
|
||||
std::size_t CustomAsset::GetHandle() const
|
||||
{
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
const CustomAssetLibrary::AssetID& CustomAsset::GetAssetId() const
|
||||
{
|
||||
return m_asset_id;
|
||||
}
|
||||
|
||||
std::size_t CustomAsset::GetByteSizeInMemory() const
|
||||
{
|
||||
std::lock_guard lk(m_info_lock);
|
||||
return m_bytes_loaded;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
@@ -17,42 +17,47 @@ namespace VideoCommon
|
||||
class CustomAsset
|
||||
{
|
||||
public:
|
||||
using ClockType = std::chrono::steady_clock;
|
||||
using TimeType = ClockType::time_point;
|
||||
|
||||
CustomAsset(std::shared_ptr<CustomAssetLibrary> library,
|
||||
const CustomAssetLibrary::AssetID& asset_id);
|
||||
const CustomAssetLibrary::AssetID& asset_id, u64 session_id);
|
||||
virtual ~CustomAsset() = default;
|
||||
CustomAsset(const CustomAsset&) = delete;
|
||||
CustomAsset(CustomAsset&&) = delete;
|
||||
CustomAsset& operator=(const CustomAsset&) = delete;
|
||||
CustomAsset& operator=(CustomAsset&&) = delete;
|
||||
|
||||
// Loads the asset from the library returning a pass/fail result
|
||||
bool Load();
|
||||
// Loads the asset from the library returning the number of bytes loaded
|
||||
std::size_t Load();
|
||||
|
||||
// Queries the last time the asset was modified or standard epoch time
|
||||
// if the asset hasn't been modified yet
|
||||
// Note: not thread safe, expected to be called by the loader
|
||||
CustomAssetLibrary::TimeType GetLastWriteTime() const;
|
||||
// Unloads the asset data, resets the bytes loaded and
|
||||
// returns the number of bytes unloaded
|
||||
std::size_t Unload();
|
||||
|
||||
// Returns the time that the data was last loaded
|
||||
const CustomAssetLibrary::TimeType& GetLastLoadedTime() const;
|
||||
TimeType GetLastLoadedTime() const;
|
||||
|
||||
// Returns an id that uniquely identifies this asset
|
||||
const CustomAssetLibrary::AssetID& GetAssetId() const;
|
||||
|
||||
// A rough estimate of how much space this asset
|
||||
// will take in memroy
|
||||
std::size_t GetByteSizeInMemory() const;
|
||||
// Returns an id that is unique to this game session
|
||||
// This is a faster form to hash and can be used
|
||||
// as an index
|
||||
std::size_t GetHandle() const;
|
||||
|
||||
protected:
|
||||
const std::shared_ptr<CustomAssetLibrary> m_owning_library;
|
||||
|
||||
private:
|
||||
virtual CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) = 0;
|
||||
virtual void UnloadImpl() = 0;
|
||||
CustomAssetLibrary::AssetID m_asset_id;
|
||||
std::size_t m_handle;
|
||||
|
||||
mutable std::mutex m_info_lock;
|
||||
std::size_t m_bytes_loaded = 0;
|
||||
CustomAssetLibrary::TimeType m_last_loaded_time = {};
|
||||
std::atomic<TimeType> m_last_loaded_time = {};
|
||||
};
|
||||
|
||||
// An abstract class that is expected to
|
||||
@@ -83,6 +88,14 @@ protected:
|
||||
bool m_loaded = false;
|
||||
mutable std::mutex m_data_lock;
|
||||
std::shared_ptr<UnderlyingType> m_data;
|
||||
|
||||
private:
|
||||
void UnloadImpl() override
|
||||
{
|
||||
std::lock_guard lk(m_data_lock);
|
||||
m_loaded = false;
|
||||
m_data.reset();
|
||||
}
|
||||
};
|
||||
|
||||
// A helper struct that contains
|
||||
@@ -96,7 +109,7 @@ template <typename AssetType>
|
||||
struct CachedAsset
|
||||
{
|
||||
std::shared_ptr<AssetType> m_asset;
|
||||
VideoCommon::CustomAssetLibrary::TimeType m_cached_write_time;
|
||||
CustomAsset::TimeType m_cached_write_time;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
CustomAssetLibrary::LoadInfo CustomAssetLibrary::LoadGameTexture(const AssetID& asset_id,
|
||||
TextureData* data)
|
||||
{
|
||||
const auto load_info = LoadTexture(asset_id, data);
|
||||
if (load_info.m_bytes_loaded == 0)
|
||||
return {};
|
||||
|
||||
if (data->m_type != TextureData::Type::Type_Texture2D)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Custom asset '{}' is not a valid game texture, it is expected to be a 2d texture "
|
||||
"but was a '{}'.",
|
||||
asset_id, data->m_type);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Note: 'LoadTexture()' ensures we have a level loaded
|
||||
for (std::size_t slice_index = 0; slice_index < data->m_texture.m_slices.size(); slice_index++)
|
||||
{
|
||||
auto& slice = data->m_texture.m_slices[slice_index];
|
||||
const auto& first_mip = slice.m_levels[0];
|
||||
|
||||
// Verify that each mip level is the correct size (divide by 2 each time).
|
||||
u32 current_mip_width = first_mip.width;
|
||||
u32 current_mip_height = first_mip.height;
|
||||
for (u32 mip_level = 1; mip_level < static_cast<u32>(slice.m_levels.size()); mip_level++)
|
||||
{
|
||||
if (current_mip_width != 1 || current_mip_height != 1)
|
||||
{
|
||||
current_mip_width = std::max(current_mip_width / 2, 1u);
|
||||
current_mip_height = std::max(current_mip_height / 2, 1u);
|
||||
|
||||
const VideoCommon::CustomTextureData::ArraySlice::Level& level = slice.m_levels[mip_level];
|
||||
if (current_mip_width == level.width && current_mip_height == level.height)
|
||||
continue;
|
||||
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Invalid custom game texture size {}x{} for texture asset {}. Slice {} with "
|
||||
"mipmap level {} "
|
||||
"must be {}x{}.",
|
||||
level.width, level.height, asset_id, slice_index, mip_level,
|
||||
current_mip_width, current_mip_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// It is invalid to have more than a single 1x1 mipmap.
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Custom game texture {} has too many 1x1 mipmaps for slice {}. Skipping extra levels.",
|
||||
asset_id, slice_index);
|
||||
}
|
||||
|
||||
// Drop this mip level and any others after it.
|
||||
while (slice.m_levels.size() > mip_level)
|
||||
slice.m_levels.pop_back();
|
||||
}
|
||||
|
||||
// All levels have to have the same format.
|
||||
if (std::ranges::any_of(slice.m_levels,
|
||||
[&first_mip](const auto& l) { return l.format != first_mip.format; }))
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO, "Custom game texture {} has inconsistent formats across mip levels for slice {}.",
|
||||
asset_id, slice_index);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return load_info;
|
||||
}
|
||||
} // namespace VideoCommon
|
||||
@@ -10,10 +10,11 @@
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class CustomTextureData;
|
||||
struct MaterialData;
|
||||
struct MeshData;
|
||||
struct PixelShaderData;
|
||||
struct TextureData;
|
||||
struct TextureAndSamplerData;
|
||||
|
||||
// This class provides functionality to load
|
||||
// specific data (like textures). Where this data
|
||||
@@ -21,28 +22,21 @@ struct TextureData;
|
||||
class CustomAssetLibrary
|
||||
{
|
||||
public:
|
||||
using TimeType = std::chrono::system_clock::time_point;
|
||||
|
||||
// The AssetID is a unique identifier for a particular asset
|
||||
using AssetID = std::string;
|
||||
|
||||
struct LoadInfo
|
||||
{
|
||||
std::size_t m_bytes_loaded = 0;
|
||||
TimeType m_load_time = {};
|
||||
std::size_t bytes_loaded = 0;
|
||||
};
|
||||
|
||||
virtual ~CustomAssetLibrary() = default;
|
||||
|
||||
// Loads a texture with a sampler and type, if there are no levels, bytes loaded will be empty
|
||||
virtual LoadInfo LoadTexture(const AssetID& asset_id, TextureAndSamplerData* data) = 0;
|
||||
|
||||
// Loads a texture, if there are no levels, bytes loaded will be empty
|
||||
virtual LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) = 0;
|
||||
|
||||
// Gets the last write time for a given asset id
|
||||
virtual TimeType GetLastAssetWriteTime(const AssetID& asset_id) const = 0;
|
||||
|
||||
// Loads a texture as a game texture, providing additional checks like confirming
|
||||
// each mip level size is correct and that the format is consistent across the data
|
||||
LoadInfo LoadGameTexture(const AssetID& asset_id, TextureData* data);
|
||||
virtual LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) = 0;
|
||||
|
||||
// Loads a pixel shader
|
||||
virtual LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) = 0;
|
||||
|
||||
@@ -1,108 +1,157 @@
|
||||
// Copyright 2023 Dolphin Emulator Project
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
|
||||
#include "Common/MemoryUtil.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Thread.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
void CustomAssetLoader::Init()
|
||||
void CustomAssetLoader::Initialize()
|
||||
{
|
||||
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_asset_load_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 (m_memory_exceeded)
|
||||
return;
|
||||
|
||||
if (ptr->Load())
|
||||
{
|
||||
std::lock_guard lk(m_asset_load_lock);
|
||||
const std::size_t asset_memory_size = ptr->GetByteSizeInMemory();
|
||||
m_total_bytes_loaded += asset_memory_size;
|
||||
m_assets_to_monitor.try_emplace(ptr->GetAssetId(), ptr);
|
||||
if (m_total_bytes_loaded > m_max_memory_available)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Asset memory exceeded with asset '{}', future assets won't load until "
|
||||
"memory is available.",
|
||||
ptr->GetAssetId());
|
||||
m_memory_exceeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ResizeWorkerThreads(2);
|
||||
}
|
||||
|
||||
void CustomAssetLoader::Shutdown()
|
||||
{
|
||||
m_asset_load_thread.StopAndCancel();
|
||||
|
||||
m_asset_monitor_thread_shutdown.Set();
|
||||
m_asset_monitor_thread.join();
|
||||
m_assets_to_monitor.clear();
|
||||
m_total_bytes_loaded = 0;
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
std::shared_ptr<GameTextureAsset>
|
||||
CustomAssetLoader::LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
bool CustomAssetLoader::StartWorkerThreads(u32 num_worker_threads)
|
||||
{
|
||||
return LoadOrCreateAsset<GameTextureAsset>(asset_id, m_game_textures, std::move(library));
|
||||
for (u32 i = 0; i < num_worker_threads; i++)
|
||||
{
|
||||
m_worker_threads.emplace_back(&CustomAssetLoader::WorkerThreadRun, this, i);
|
||||
}
|
||||
|
||||
return HasWorkerThreads();
|
||||
}
|
||||
|
||||
std::shared_ptr<PixelShaderAsset>
|
||||
CustomAssetLoader::LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
bool CustomAssetLoader::ResizeWorkerThreads(u32 num_worker_threads)
|
||||
{
|
||||
return LoadOrCreateAsset<PixelShaderAsset>(asset_id, m_pixel_shaders, std::move(library));
|
||||
if (m_worker_threads.size() == num_worker_threads)
|
||||
return true;
|
||||
|
||||
StopWorkerThreads();
|
||||
return StartWorkerThreads(num_worker_threads);
|
||||
}
|
||||
|
||||
std::shared_ptr<MaterialAsset>
|
||||
CustomAssetLoader::LoadMaterial(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
bool CustomAssetLoader::HasWorkerThreads() const
|
||||
{
|
||||
return LoadOrCreateAsset<MaterialAsset>(asset_id, m_materials, std::move(library));
|
||||
return !m_worker_threads.empty();
|
||||
}
|
||||
|
||||
std::shared_ptr<MeshAsset> CustomAssetLoader::LoadMesh(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
void CustomAssetLoader::StopWorkerThreads()
|
||||
{
|
||||
return LoadOrCreateAsset<MeshAsset>(asset_id, m_meshes, std::move(library));
|
||||
if (!HasWorkerThreads())
|
||||
return;
|
||||
|
||||
// Signal worker threads to stop, and wake all of them.
|
||||
{
|
||||
std::lock_guard guard(m_assets_to_load_lock);
|
||||
m_exit_flag.Set();
|
||||
m_worker_thread_wake.notify_all();
|
||||
}
|
||||
|
||||
// Wait for worker threads to exit.
|
||||
for (std::thread& thr : m_worker_threads)
|
||||
thr.join();
|
||||
m_worker_threads.clear();
|
||||
m_exit_flag.Clear();
|
||||
}
|
||||
|
||||
void CustomAssetLoader::WorkerThreadRun(u32 thread_index)
|
||||
{
|
||||
Common::SetCurrentThreadName(fmt::format("Asset Loader {}", thread_index).c_str());
|
||||
|
||||
std::unique_lock load_lock(m_assets_to_load_lock);
|
||||
while (true)
|
||||
{
|
||||
m_worker_thread_wake.wait(load_lock,
|
||||
[&] { return !m_assets_to_load.empty() || m_exit_flag.IsSet(); });
|
||||
|
||||
if (m_exit_flag.IsSet())
|
||||
return;
|
||||
|
||||
// If more memory than allowed has already been loaded, we will load nothing more
|
||||
// until the next ScheduleAssetsToLoad from Manager.
|
||||
if (m_change_in_memory > m_allowed_memory)
|
||||
{
|
||||
m_assets_to_load.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* const item = m_assets_to_load.front();
|
||||
m_assets_to_load.pop_front();
|
||||
|
||||
// Make sure another thread isn't loading this handle.
|
||||
if (!m_handles_in_progress.insert(item->GetHandle()).second)
|
||||
continue;
|
||||
|
||||
load_lock.unlock();
|
||||
|
||||
// Unload previously loaded asset.
|
||||
m_change_in_memory -= item->Unload();
|
||||
|
||||
const std::size_t bytes_loaded = item->Load();
|
||||
m_change_in_memory += s64(bytes_loaded);
|
||||
|
||||
load_lock.lock();
|
||||
|
||||
{
|
||||
INFO_LOG_FMT(VIDEO, "CustomAssetLoader thread {} loaded: {} ({})", thread_index,
|
||||
item->GetAssetId(), UICommon::FormatSize(bytes_loaded));
|
||||
|
||||
std::lock_guard lk{m_assets_loaded_lock};
|
||||
m_asset_handles_loaded.emplace_back(item->GetHandle(), bytes_loaded > 0);
|
||||
|
||||
// Make sure no other threads try to re-process this item.
|
||||
// Manager will take the handles and re-ScheduleAssetsToLoad based on timestamps if needed.
|
||||
std::erase(m_assets_to_load, item);
|
||||
}
|
||||
|
||||
m_handles_in_progress.erase(item->GetHandle());
|
||||
}
|
||||
}
|
||||
|
||||
auto CustomAssetLoader::TakeLoadResults() -> LoadResults
|
||||
{
|
||||
std::lock_guard guard(m_assets_loaded_lock);
|
||||
return {std::move(m_asset_handles_loaded), m_change_in_memory.exchange(0)};
|
||||
}
|
||||
|
||||
void CustomAssetLoader::ScheduleAssetsToLoad(std::list<CustomAsset*> assets_to_load,
|
||||
u64 allowed_memory)
|
||||
{
|
||||
if (assets_to_load.empty()) [[unlikely]]
|
||||
return;
|
||||
|
||||
// There's new assets to process, notify worker threads
|
||||
std::lock_guard guard(m_assets_to_load_lock);
|
||||
m_allowed_memory = allowed_memory;
|
||||
m_assets_to_load = std::move(assets_to_load);
|
||||
m_worker_thread_wake.notify_all();
|
||||
}
|
||||
|
||||
void CustomAssetLoader::Reset(bool restart_worker_threads)
|
||||
{
|
||||
const std::size_t worker_thread_count = m_worker_threads.size();
|
||||
StopWorkerThreads();
|
||||
|
||||
m_assets_to_load.clear();
|
||||
m_asset_handles_loaded.clear();
|
||||
m_allowed_memory = 0;
|
||||
m_change_in_memory = 0;
|
||||
|
||||
if (restart_worker_threads)
|
||||
{
|
||||
StartWorkerThreads(static_cast<u32>(worker_thread_count));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
// Copyright 2023 Dolphin Emulator Project
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#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
|
||||
// This class takes any number of assets
|
||||
// and loads them across a configurable
|
||||
// thread pool
|
||||
class CustomAssetLoader
|
||||
{
|
||||
public:
|
||||
@@ -32,77 +29,54 @@ public:
|
||||
CustomAssetLoader& operator=(const CustomAssetLoader&) = delete;
|
||||
CustomAssetLoader& operator=(CustomAssetLoader&&) = delete;
|
||||
|
||||
void Init();
|
||||
void Initialize();
|
||||
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);
|
||||
using AssetHandle = std::pair<std::size_t, bool>;
|
||||
struct LoadResults
|
||||
|
||||
std::shared_ptr<PixelShaderAsset> LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library);
|
||||
{
|
||||
std::vector<AssetHandle> asset_handles;
|
||||
s64 change_in_memory;
|
||||
};
|
||||
|
||||
std::shared_ptr<MaterialAsset> LoadMaterial(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library);
|
||||
// Returns a vector of loaded asset handle / loaded result pairs
|
||||
// and the change in memory.
|
||||
LoadResults TakeLoadResults();
|
||||
|
||||
std::shared_ptr<MeshAsset> LoadMesh(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library);
|
||||
// Schedule assets to load on the worker threads
|
||||
// and set how much memory is available for loading these additional assets.
|
||||
void ScheduleAssetsToLoad(std::list<CustomAsset*> assets_to_load, u64 allowed_memory);
|
||||
|
||||
void Reset(bool restart_worker_threads = true);
|
||||
|
||||
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;
|
||||
}
|
||||
bool StartWorkerThreads(u32 num_worker_threads);
|
||||
bool ResizeWorkerThreads(u32 num_worker_threads);
|
||||
bool HasWorkerThreads() const;
|
||||
void StopWorkerThreads();
|
||||
|
||||
static constexpr auto TIME_BETWEEN_ASSET_MONITOR_CHECKS = std::chrono::milliseconds{500};
|
||||
void WorkerThreadRun(u32 thread_index);
|
||||
|
||||
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;
|
||||
Common::Flag m_exit_flag;
|
||||
|
||||
std::size_t m_total_bytes_loaded = 0;
|
||||
std::size_t m_max_memory_available = 0;
|
||||
std::atomic_bool m_memory_exceeded = false;
|
||||
std::vector<std::thread> m_worker_threads;
|
||||
|
||||
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<CustomAsset>> m_assets_to_monitor;
|
||||
std::mutex m_assets_to_load_lock;
|
||||
std::list<CustomAsset*> m_assets_to_load;
|
||||
|
||||
// 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;
|
||||
std::condition_variable m_worker_thread_wake;
|
||||
|
||||
std::vector<AssetHandle> m_asset_handles_loaded;
|
||||
|
||||
// Memory available to load new assets.
|
||||
s64 m_allowed_memory = 0;
|
||||
|
||||
// Change in memory from just-loaded/unloaded asset results yet to be taken by the Manager.
|
||||
std::atomic<s64> m_change_in_memory = 0;
|
||||
|
||||
std::mutex m_assets_loaded_lock;
|
||||
|
||||
std::set<std::size_t> m_handles_in_progress;
|
||||
};
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MemoryUtil.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
void CustomResourceManager::Initialize()
|
||||
{
|
||||
// Use half of available system memory but leave at least 2GiB unused for system stability.
|
||||
constexpr size_t must_keep_unused = 2 * size_t(1024 * 1024 * 1024);
|
||||
|
||||
const size_t sys_mem = Common::MemPhysical();
|
||||
const size_t keep_unused_mem = std::max(sys_mem / 2, std::min(sys_mem, must_keep_unused));
|
||||
|
||||
m_max_ram_available = sys_mem - keep_unused_mem;
|
||||
|
||||
if (m_max_ram_available == 0)
|
||||
ERROR_LOG_FMT(VIDEO, "Not enough system memory for custom resources.");
|
||||
|
||||
m_asset_loader.Initialize();
|
||||
|
||||
m_xfb_event =
|
||||
AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(); }, "CustomResourceManager");
|
||||
}
|
||||
|
||||
void CustomResourceManager::Shutdown()
|
||||
{
|
||||
Reset();
|
||||
|
||||
m_asset_loader.Shutdown();
|
||||
}
|
||||
|
||||
void CustomResourceManager::Reset()
|
||||
{
|
||||
m_asset_loader.Reset(true);
|
||||
|
||||
m_active_assets = {};
|
||||
m_pending_assets = {};
|
||||
m_asset_handle_to_data.clear();
|
||||
m_asset_id_to_handle.clear();
|
||||
m_texture_data_asset_cache.clear();
|
||||
m_dirty_assets.clear();
|
||||
m_ram_used = 0;
|
||||
}
|
||||
|
||||
void CustomResourceManager::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id)
|
||||
{
|
||||
std::lock_guard guard(m_dirty_mutex);
|
||||
m_dirty_assets.insert(asset_id);
|
||||
}
|
||||
|
||||
CustomResourceManager::TextureTimePair CustomResourceManager::GetTextureDataFromAsset(
|
||||
const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
auto& resource = m_texture_data_asset_cache[asset_id];
|
||||
if (resource.asset_data != nullptr &&
|
||||
resource.asset_data->load_status == AssetData::LoadStatus::ResourceDataAvailable)
|
||||
{
|
||||
m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset);
|
||||
return {resource.texture_data, resource.asset->GetLastLoadedTime()};
|
||||
}
|
||||
|
||||
// If there is an error, don't try and load again until the error is fixed
|
||||
if (resource.asset_data != nullptr && resource.asset_data->has_load_error)
|
||||
return {};
|
||||
|
||||
LoadTextureDataAsset(asset_id, std::move(library), &resource);
|
||||
m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void CustomResourceManager::LoadTextureDataAsset(
|
||||
const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library, InternalTextureDataResource* resource)
|
||||
{
|
||||
if (!resource->asset)
|
||||
{
|
||||
resource->asset =
|
||||
CreateAsset<TextureAsset>(asset_id, AssetData::AssetType::TextureData, std::move(library));
|
||||
resource->asset_data = &m_asset_handle_to_data[resource->asset->GetHandle()];
|
||||
}
|
||||
|
||||
auto texture_data = resource->asset->GetData();
|
||||
if (!texture_data || resource->asset_data->load_status == AssetData::LoadStatus::PendingReload)
|
||||
{
|
||||
// Tell the system we are still interested in loading this asset
|
||||
const auto asset_handle = resource->asset->GetHandle();
|
||||
m_pending_assets.MakeAssetHighestPriority(asset_handle,
|
||||
m_asset_handle_to_data[asset_handle].asset.get());
|
||||
}
|
||||
else if (resource->asset_data->load_status == AssetData::LoadStatus::LoadFinished)
|
||||
{
|
||||
resource->texture_data = std::move(texture_data);
|
||||
resource->asset_data->load_status = AssetData::LoadStatus::ResourceDataAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::XFBTriggered()
|
||||
{
|
||||
ProcessDirtyAssets();
|
||||
ProcessLoadedAssets();
|
||||
|
||||
if (m_ram_used > m_max_ram_available)
|
||||
{
|
||||
RemoveAssetsUntilBelowMemoryLimit();
|
||||
}
|
||||
|
||||
if (m_pending_assets.IsEmpty())
|
||||
return;
|
||||
|
||||
if (m_ram_used > m_max_ram_available)
|
||||
return;
|
||||
|
||||
const u64 allowed_memory = m_max_ram_available - m_ram_used;
|
||||
m_asset_loader.ScheduleAssetsToLoad(m_pending_assets.Elements(), allowed_memory);
|
||||
}
|
||||
|
||||
void CustomResourceManager::ProcessDirtyAssets()
|
||||
{
|
||||
decltype(m_dirty_assets) dirty_assets;
|
||||
|
||||
if (const auto lk = std::unique_lock{m_dirty_mutex, std::try_to_lock})
|
||||
std::swap(dirty_assets, m_dirty_assets);
|
||||
|
||||
const auto now = CustomAsset::ClockType::now();
|
||||
for (const auto& asset_id : dirty_assets)
|
||||
{
|
||||
if (const auto it = m_asset_id_to_handle.find(asset_id); it != m_asset_id_to_handle.end())
|
||||
{
|
||||
const auto asset_handle = it->second;
|
||||
AssetData& asset_data = m_asset_handle_to_data[asset_handle];
|
||||
asset_data.load_status = AssetData::LoadStatus::PendingReload;
|
||||
asset_data.load_request_time = now;
|
||||
|
||||
// Asset was reloaded, clear any errors we might have
|
||||
asset_data.has_load_error = false;
|
||||
|
||||
m_pending_assets.InsertAsset(it->second, asset_data.asset.get());
|
||||
|
||||
DEBUG_LOG_FMT(VIDEO, "Dirty asset pending reload: {}", asset_data.asset->GetAssetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::ProcessLoadedAssets()
|
||||
{
|
||||
const auto load_results = m_asset_loader.TakeLoadResults();
|
||||
|
||||
// Update the ram with the change in memory from the loader
|
||||
//
|
||||
// Note: Assets with outstanding reload requests will have
|
||||
// two copies in memory temporarily (the old data stored in
|
||||
// the asset shared_ptr that the resource manager owns, and
|
||||
// the new data loaded from the loader in the asset's shared_ptr)
|
||||
// This temporary duplication will not be reflected in the
|
||||
// resource manager's ram used
|
||||
m_ram_used += load_results.change_in_memory;
|
||||
|
||||
for (const auto& [handle, load_successful] : load_results.asset_handles)
|
||||
{
|
||||
AssetData& asset_data = m_asset_handle_to_data[handle];
|
||||
|
||||
// If we have a reload request that is newer than our loaded time
|
||||
// we need to wait for another reload.
|
||||
if (asset_data.load_request_time > asset_data.asset->GetLastLoadedTime())
|
||||
continue;
|
||||
|
||||
m_pending_assets.RemoveAsset(handle);
|
||||
|
||||
asset_data.load_request_time = {};
|
||||
if (!load_successful)
|
||||
{
|
||||
asset_data.has_load_error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_active_assets.InsertAsset(handle, asset_data.asset.get());
|
||||
asset_data.load_status = AssetData::LoadStatus::LoadFinished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit()
|
||||
{
|
||||
const u64 threshold_ram = m_max_ram_available * 8 / 10;
|
||||
|
||||
if (m_ram_used > threshold_ram)
|
||||
{
|
||||
INFO_LOG_FMT(VIDEO, "Memory usage over threshold: {}", UICommon::FormatSize(m_ram_used));
|
||||
}
|
||||
|
||||
// Clear out least recently used resources until
|
||||
// we get safely in our threshold
|
||||
while (m_ram_used > threshold_ram && m_active_assets.Size() > 0)
|
||||
{
|
||||
auto* const asset = m_active_assets.RemoveLowestPriorityAsset();
|
||||
|
||||
AssetData& asset_data = m_asset_handle_to_data[asset->GetHandle()];
|
||||
|
||||
// Remove the resource manager's cached entry with its asset data
|
||||
if (asset_data.type == AssetData::AssetType::TextureData)
|
||||
{
|
||||
m_texture_data_asset_cache.erase(asset->GetAssetId());
|
||||
}
|
||||
// Remove the asset's copy
|
||||
const std::size_t bytes_unloaded = asset_data.asset->Unload();
|
||||
m_ram_used -= bytes_unloaded;
|
||||
|
||||
asset_data.load_status = AssetData::LoadStatus::Unloaded;
|
||||
asset_data.load_request_time = {};
|
||||
|
||||
INFO_LOG_FMT(VIDEO, "Unloading asset: {} ({})", asset_data.asset->GetAssetId(),
|
||||
UICommon::FormatSize(bytes_unloaded));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -0,0 +1,215 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/HookableEvent.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class TextureAsset;
|
||||
|
||||
// The resource manager manages custom resources (textures, shaders, meshes)
|
||||
// called assets. These assets are loaded using a priority system,
|
||||
// where assets requested more often gets loaded first. This system
|
||||
// also tracks memory usage and if memory usage goes over a calculated limit,
|
||||
// then assets will be purged with older assets being targeted first.
|
||||
class CustomResourceManager
|
||||
{
|
||||
public:
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void Reset();
|
||||
|
||||
// Request that an asset be reloaded
|
||||
void MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id);
|
||||
|
||||
void XFBTriggered();
|
||||
|
||||
using TextureTimePair = std::pair<std::shared_ptr<CustomTextureData>, CustomAsset::TimeType>;
|
||||
|
||||
// Returns a pair with the custom texture data and the time it was last loaded
|
||||
// Callees are not expected to hold onto the shared_ptr as that will prevent
|
||||
// the resource manager from being able to properly release data
|
||||
TextureTimePair GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||
|
||||
private:
|
||||
// A generic interface to describe an assets' type
|
||||
// and load state
|
||||
struct AssetData
|
||||
{
|
||||
std::unique_ptr<CustomAsset> asset;
|
||||
CustomAsset::TimeType load_request_time = {};
|
||||
bool has_load_error = false;
|
||||
|
||||
enum class AssetType
|
||||
{
|
||||
TextureData
|
||||
};
|
||||
AssetType type;
|
||||
|
||||
enum class LoadStatus
|
||||
{
|
||||
PendingReload,
|
||||
LoadFinished,
|
||||
ResourceDataAvailable,
|
||||
Unloaded,
|
||||
};
|
||||
LoadStatus load_status = LoadStatus::PendingReload;
|
||||
};
|
||||
|
||||
// A structure to represent some raw texture data
|
||||
// (this data hasn't hit the GPU yet, used for custom textures)
|
||||
struct InternalTextureDataResource
|
||||
{
|
||||
AssetData* asset_data = nullptr;
|
||||
VideoCommon::TextureAsset* asset = nullptr;
|
||||
std::shared_ptr<CustomTextureData> texture_data;
|
||||
};
|
||||
|
||||
void LoadTextureDataAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
InternalTextureDataResource* resource);
|
||||
|
||||
void ProcessDirtyAssets();
|
||||
void ProcessLoadedAssets();
|
||||
void RemoveAssetsUntilBelowMemoryLimit();
|
||||
|
||||
template <typename T>
|
||||
T* CreateAsset(const CustomAssetLibrary::AssetID& asset_id, AssetData::AssetType asset_type,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
const auto [it, added] =
|
||||
m_asset_id_to_handle.try_emplace(asset_id, m_asset_handle_to_data.size());
|
||||
|
||||
if (added)
|
||||
{
|
||||
AssetData asset_data;
|
||||
asset_data.asset = std::make_unique<T>(library, asset_id, it->second);
|
||||
asset_data.type = asset_type;
|
||||
asset_data.load_request_time = {};
|
||||
asset_data.has_load_error = false;
|
||||
|
||||
m_asset_handle_to_data.insert_or_assign(it->second, std::move(asset_data));
|
||||
}
|
||||
auto& asset_data_from_handle = m_asset_handle_to_data[it->second];
|
||||
asset_data_from_handle.load_status = AssetData::LoadStatus::PendingReload;
|
||||
|
||||
return static_cast<T*>(asset_data_from_handle.asset.get());
|
||||
}
|
||||
|
||||
// Maintains a priority-sorted list of assets.
|
||||
// Used to figure out which assets to load or unload first.
|
||||
// Most recently used assets get marked with highest priority.
|
||||
class AssetPriorityQueue
|
||||
{
|
||||
public:
|
||||
const auto& Elements() const { return m_assets; }
|
||||
|
||||
// Inserts or moves the asset to the top of the queue.
|
||||
void MakeAssetHighestPriority(u64 asset_handle, CustomAsset* asset)
|
||||
{
|
||||
RemoveAsset(asset_handle);
|
||||
m_assets.push_front(asset);
|
||||
|
||||
// See CreateAsset for how a handle gets defined
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
m_iterator_lookup.resize(asset_handle + 1, m_assets.end());
|
||||
|
||||
m_iterator_lookup[asset_handle] = m_assets.begin();
|
||||
}
|
||||
|
||||
// Inserts an asset at lowest priority or
|
||||
// does nothing if asset is already in the queue.
|
||||
void InsertAsset(u64 asset_handle, CustomAsset* asset)
|
||||
{
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
m_iterator_lookup.resize(asset_handle + 1, m_assets.end());
|
||||
|
||||
if (m_iterator_lookup[asset_handle] == m_assets.end())
|
||||
{
|
||||
m_assets.push_back(asset);
|
||||
m_iterator_lookup[asset_handle] = std::prev(m_assets.end());
|
||||
}
|
||||
}
|
||||
|
||||
CustomAsset* RemoveLowestPriorityAsset()
|
||||
{
|
||||
if (m_assets.empty()) [[unlikely]]
|
||||
return nullptr;
|
||||
auto* const ret = m_assets.back();
|
||||
if (ret != nullptr)
|
||||
{
|
||||
m_iterator_lookup[ret->GetHandle()] = m_assets.end();
|
||||
}
|
||||
m_assets.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RemoveAsset(u64 asset_handle)
|
||||
{
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
return;
|
||||
|
||||
const auto iter = m_iterator_lookup[asset_handle];
|
||||
if (iter != m_assets.end())
|
||||
{
|
||||
m_assets.erase(iter);
|
||||
m_iterator_lookup[asset_handle] = m_assets.end();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return m_assets.empty(); }
|
||||
|
||||
std::size_t Size() const { return m_assets.size(); }
|
||||
|
||||
private:
|
||||
std::list<CustomAsset*> m_assets;
|
||||
|
||||
// Handle-to-iterator lookup for fast access.
|
||||
// Grows as needed on insert.
|
||||
std::vector<decltype(m_assets)::iterator> m_iterator_lookup;
|
||||
};
|
||||
|
||||
// Assets that are currently active in memory, in order of most recently used by the game.
|
||||
AssetPriorityQueue m_active_assets;
|
||||
|
||||
// Assets that need to be loaded.
|
||||
// e.g. Because the game tried to use them or because they changed on disk.
|
||||
// Ordered by most recently used.
|
||||
AssetPriorityQueue m_pending_assets;
|
||||
|
||||
std::map<std::size_t, AssetData> m_asset_handle_to_data;
|
||||
std::map<CustomAssetLibrary::AssetID, std::size_t> m_asset_id_to_handle;
|
||||
|
||||
// Memory used by currently "loaded" assets.
|
||||
u64 m_ram_used = 0;
|
||||
|
||||
// A calculated amount of memory to avoid exceeding.
|
||||
u64 m_max_ram_available = 0;
|
||||
|
||||
std::map<CustomAssetLibrary::AssetID, InternalTextureDataResource> m_texture_data_asset_cache;
|
||||
|
||||
std::mutex m_dirty_mutex;
|
||||
std::set<CustomAssetLibrary::AssetID> m_dirty_assets;
|
||||
|
||||
CustomAssetLoader m_asset_loader;
|
||||
|
||||
Common::EventHook m_xfb_event;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -13,30 +13,19 @@
|
||||
#include "Common/JsonUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/Assets/MaterialAsset.h"
|
||||
#include "VideoCommon/Assets/MeshAsset.h"
|
||||
#include "VideoCommon/Assets/ShaderAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAssetUtils.h"
|
||||
#include "VideoCommon/RenderState.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::chrono::system_clock::time_point FileTimeToSysTime(std::filesystem::file_time_type file_time)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return std::chrono::clock_cast<std::chrono::system_clock>(file_time);
|
||||
#else
|
||||
// Note: all compilers should switch to chrono::clock_cast
|
||||
// once it is available for use
|
||||
const auto system_time_now = std::chrono::system_clock::now();
|
||||
const auto file_time_now = decltype(file_time)::clock::now();
|
||||
return std::chrono::time_point_cast<std::chrono::system_clock::duration>(
|
||||
file_time - file_time_now + system_time_now);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t GetAssetSize(const CustomTextureData& data)
|
||||
{
|
||||
std::size_t total = 0;
|
||||
@@ -50,30 +39,6 @@ std::size_t GetAssetSize(const CustomTextureData& data)
|
||||
return total;
|
||||
}
|
||||
} // namespace
|
||||
CustomAssetLibrary::TimeType
|
||||
DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) const
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
|
||||
iter != m_assetid_to_asset_map_path.end())
|
||||
{
|
||||
const auto& asset_map_path = iter->second;
|
||||
CustomAssetLibrary::TimeType max_entry;
|
||||
for (const auto& [key, value] : asset_map_path)
|
||||
{
|
||||
std::error_code ec;
|
||||
const auto tp = std::filesystem::last_write_time(value, ec);
|
||||
if (ec)
|
||||
continue;
|
||||
auto tp_sys = FileTimeToSysTime(tp);
|
||||
if (tp_sys > max_entry)
|
||||
max_entry = tp_sys;
|
||||
}
|
||||
return max_entry;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadPixelShader(const AssetID& asset_id,
|
||||
PixelShaderData* data)
|
||||
@@ -158,7 +123,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadPixelShader(const
|
||||
if (!PixelShaderData::FromJson(asset_id, root_obj, data))
|
||||
return {};
|
||||
|
||||
return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)};
|
||||
return LoadInfo{approx_mem_size};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const AssetID& asset_id,
|
||||
@@ -216,7 +181,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const As
|
||||
return {};
|
||||
}
|
||||
|
||||
return LoadInfo{metadata_size, GetLastAssetWriteTime(asset_id)};
|
||||
return LoadInfo{metadata_size};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMesh(const AssetID& asset_id,
|
||||
@@ -311,11 +276,41 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMesh(const AssetI
|
||||
if (!MeshData::FromJson(asset_id, root_obj, data))
|
||||
return {};
|
||||
|
||||
return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)};
|
||||
return LoadInfo{approx_mem_size};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
|
||||
TextureData* data)
|
||||
CustomTextureData* data)
|
||||
{
|
||||
const auto asset_map = GetAssetMapForID(asset_id);
|
||||
if (asset_map.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - raw texture expected to have one or two files mapped!",
|
||||
asset_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto texture_path = asset_map.find("texture");
|
||||
|
||||
if (texture_path == asset_map.end())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a texture entry mapped!", asset_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!LoadTextureDataFromFile(asset_id, texture_path->second,
|
||||
TextureAndSamplerData::Type::Type_Texture2D, data))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
if (!PurgeInvalidMipsFromTextureData(asset_id, data))
|
||||
return {};
|
||||
|
||||
return LoadInfo{GetAssetSize(*data)};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
|
||||
TextureAndSamplerData* data)
|
||||
{
|
||||
const auto asset_map = GetAssetMapForID(asset_id);
|
||||
|
||||
@@ -368,7 +363,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
|
||||
}
|
||||
|
||||
const auto& root_obj = root.get<picojson::object>();
|
||||
if (!TextureData::FromJson(asset_id, root_obj, data))
|
||||
if (!TextureAndSamplerData::FromJson(asset_id, root_obj, data))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
@@ -376,128 +371,62 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
|
||||
else
|
||||
{
|
||||
data->m_sampler = RenderState::GetLinearSamplerState();
|
||||
data->m_type = TextureData::Type::Type_Texture2D;
|
||||
data->m_type = TextureAndSamplerData::Type::Type_Texture2D;
|
||||
}
|
||||
|
||||
auto ext = PathToString(texture_path->second.extension());
|
||||
Common::ToLower(&ext);
|
||||
if (ext == ".dds")
|
||||
{
|
||||
if (!LoadDDSTexture(&data->m_texture, PathToString(texture_path->second)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load dds texture!", asset_id);
|
||||
return {};
|
||||
}
|
||||
if (!LoadTextureDataFromFile(asset_id, texture_path->second, data->m_type, &data->m_texture))
|
||||
return {};
|
||||
if (!PurgeInvalidMipsFromTextureData(asset_id, &data->m_texture))
|
||||
return {};
|
||||
|
||||
if (data->m_texture.m_slices.empty()) [[unlikely]]
|
||||
data->m_texture.m_slices.push_back({});
|
||||
|
||||
if (!LoadMips(texture_path->second, &data->m_texture.m_slices[0]))
|
||||
return {};
|
||||
|
||||
return LoadInfo{GetAssetSize(data->m_texture) + metadata_size, GetLastAssetWriteTime(asset_id)};
|
||||
}
|
||||
else if (ext == ".png")
|
||||
{
|
||||
// PNG could support more complicated texture types in the future
|
||||
// but for now just error
|
||||
if (data->m_type != TextureData::Type::Type_Texture2D)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - PNG is not supported for texture type '{}'!",
|
||||
asset_id, data->m_type);
|
||||
return {};
|
||||
}
|
||||
|
||||
// If we have no slices, create one
|
||||
if (data->m_texture.m_slices.empty())
|
||||
data->m_texture.m_slices.push_back({});
|
||||
|
||||
auto& slice = data->m_texture.m_slices[0];
|
||||
// If we have no levels, create one to pass into LoadPNGTexture
|
||||
if (slice.m_levels.empty())
|
||||
slice.m_levels.push_back({});
|
||||
|
||||
if (!LoadPNGTexture(&slice.m_levels[0], PathToString(texture_path->second)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load png texture!", asset_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!LoadMips(texture_path->second, &slice))
|
||||
return {};
|
||||
|
||||
return LoadInfo{GetAssetSize(data->m_texture) + metadata_size, GetLastAssetWriteTime(asset_id)};
|
||||
}
|
||||
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - extension '{}' unknown!", asset_id, ext);
|
||||
return {};
|
||||
return LoadInfo{GetAssetSize(data->m_texture) + metadata_size};
|
||||
}
|
||||
|
||||
void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id,
|
||||
AssetMap asset_path_map)
|
||||
VideoCommon::Assets::AssetMap asset_path_map)
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map);
|
||||
}
|
||||
|
||||
bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_path,
|
||||
CustomTextureData::ArraySlice* data)
|
||||
{
|
||||
if (!data) [[unlikely]]
|
||||
return false;
|
||||
|
||||
std::string path;
|
||||
std::string filename;
|
||||
std::string extension;
|
||||
SplitPath(PathToString(asset_path), &path, &filename, &extension);
|
||||
|
||||
std::string extension_lower = extension;
|
||||
Common::ToLower(&extension_lower);
|
||||
|
||||
// Load additional mip levels
|
||||
for (u32 mip_level = static_cast<u32>(data->m_levels.size());; mip_level++)
|
||||
VideoCommon::Assets::AssetMap previous_asset_map;
|
||||
{
|
||||
const auto mip_level_filename = filename + fmt::format("_mip{}", mip_level);
|
||||
|
||||
const auto full_path = path + mip_level_filename + extension;
|
||||
if (!File::Exists(full_path))
|
||||
return true;
|
||||
|
||||
VideoCommon::CustomTextureData::ArraySlice::Level level;
|
||||
if (extension_lower == ".dds")
|
||||
{
|
||||
if (!LoadDDSTexture(&level, full_path, mip_level))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' failed to load", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (extension_lower == ".png")
|
||||
{
|
||||
if (!LoadPNGTexture(&level, full_path))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' failed to load", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' has unsupported extension", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
data->m_levels.push_back(std::move(level));
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
previous_asset_map = m_asset_id_to_asset_map_path[asset_id];
|
||||
}
|
||||
|
||||
return true;
|
||||
{
|
||||
std::lock_guard lk(m_path_map_lock);
|
||||
for (const auto& [name, path] : previous_asset_map)
|
||||
{
|
||||
m_path_to_asset_id.erase(PathToString(path));
|
||||
}
|
||||
|
||||
for (const auto& [name, path] : asset_path_map)
|
||||
{
|
||||
m_path_to_asset_id[PathToString(path)] = asset_id;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
m_asset_id_to_asset_map_path[asset_id] = std::move(asset_path_map);
|
||||
}
|
||||
}
|
||||
|
||||
DirectFilesystemAssetLibrary::AssetMap
|
||||
void DirectFilesystemAssetLibrary::PathModified(std::string_view path)
|
||||
{
|
||||
std::lock_guard lk(m_path_map_lock);
|
||||
if (const auto iter = m_path_to_asset_id.find(path); iter != m_path_to_asset_id.end())
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& resource_manager = system.GetCustomResourceManager();
|
||||
resource_manager.MarkAssetDirty(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
VideoCommon::Assets::AssetMap
|
||||
DirectFilesystemAssetLibrary::GetAssetMapForID(const AssetID& asset_id) const
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
|
||||
iter != m_assetid_to_asset_map_path.end())
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
if (auto iter = m_asset_id_to_asset_map_path.find(asset_id);
|
||||
iter != m_asset_id_to_asset_map_path.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user