diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 01b74f3c06..bdbd579c6f 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -631,6 +631,8 @@ + + @@ -1241,6 +1243,8 @@ + + diff --git a/Source/Core/VideoCommon/Assets/CustomAsset.cpp b/Source/Core/VideoCommon/Assets/CustomAsset.cpp new file mode 100644 index 0000000000..545b60e093 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/CustomAsset.cpp @@ -0,0 +1,45 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/CustomAsset.h" + +namespace VideoCommon +{ +CustomAsset::CustomAsset(std::shared_ptr library, + const CustomAssetLibrary::AssetID& asset_id) + : m_owning_library(std::move(library)), m_asset_id(asset_id) +{ +} + +bool CustomAsset::Load() +{ + const auto load_information = LoadImpl(m_asset_id); + if (load_information.m_bytes_loaded > 0) + { + 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 +{ + return m_last_loaded_time; +} + +const CustomAssetLibrary::AssetID& CustomAsset::GetAssetId() const +{ + return m_asset_id; +} + +std::size_t CustomAsset::GetByteSizeInMemory() const +{ + return m_bytes_loaded; +} + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAsset.h b/Source/Core/VideoCommon/Assets/CustomAsset.h new file mode 100644 index 0000000000..d3ba5226da --- /dev/null +++ b/Source/Core/VideoCommon/Assets/CustomAsset.h @@ -0,0 +1,81 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" + +#include +#include +#include + +namespace VideoCommon +{ +// An abstract class that provides operations for loading +// data from a 'CustomAssetLibrary' +class CustomAsset +{ +public: + CustomAsset(std::shared_ptr library, + const CustomAssetLibrary::AssetID& asset_id); + virtual ~CustomAsset() = default; + CustomAsset(const CustomAsset&) = default; + CustomAsset(CustomAsset&&) = default; + CustomAsset& operator=(const CustomAsset&) = default; + CustomAsset& operator=(CustomAsset&&) = default; + + // Loads the asset from the library returning a pass/fail result + bool Load(); + + // Queries the last time the asset was modified or standard epoch time + // if the asset hasn't been modified yet + CustomAssetLibrary::TimeType GetLastWriteTime() const; + + // Returns the time that the data was last loaded + const CustomAssetLibrary::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; + +protected: + const std::shared_ptr m_owning_library; + +private: + virtual CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) = 0; + CustomAssetLibrary::AssetID m_asset_id; + std::size_t m_bytes_loaded = 0; + CustomAssetLibrary::TimeType m_last_loaded_time; +}; + +// An abstract class that is expected to +// be the base class for all assets +// It provides a simple interface for +// verifying that an asset data of type +// 'UnderlyingType' is loaded by +// checking against 'GetData()' +template +class CustomLoadableAsset : public CustomAsset +{ +public: + using CustomAsset::CustomAsset; + + const UnderlyingType* GetData() const + { + std::lock_guard lk(m_lock); + if (m_loaded) + return &m_data; + return nullptr; + } + +protected: + bool m_loaded = false; + mutable std::mutex m_lock; + UnderlyingType m_data; +}; + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp new file mode 100644 index 0000000000..062f5801e1 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp @@ -0,0 +1,82 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/CustomAssetLibrary.h" + +#include + +#include "Common/Logging/Log.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.h" + +namespace VideoCommon +{ +namespace +{ +std::size_t GetAssetSize(const CustomTextureData& data) +{ + std::size_t total = 0; + for (const auto& level : data.m_levels) + { + total += level.data.size(); + } + return total; +} +} // namespace +CustomAssetLibrary::LoadInfo CustomAssetLibrary::LoadGameTexture(const AssetID& asset_id, + CustomTextureData* data) +{ + const auto load_info = LoadTexture(asset_id, data); + if (load_info.m_bytes_loaded == 0) + return {}; + + // Note: 'LoadTexture()' ensures we have a level loaded + const auto& first_mip = data->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(data->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::Level& level = data->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 {}. Mipmap level {} " + "must be {}x{}.", + level.width, level.height, asset_id, 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. Skipping extra levels.", + asset_id); + } + + // Drop this mip level and any others after it. + while (data->m_levels.size() > mip_level) + data->m_levels.pop_back(); + } + + // All levels have to have the same format. + if (std::any_of(data->m_levels.begin(), data->m_levels.end(), + [&first_mip](const VideoCommon::CustomTextureData::Level& l) { + return l.format != first_mip.format; + })) + { + ERROR_LOG_FMT(VIDEO, "Custom game texture {} has inconsistent formats across mip levels.", + asset_id); + + return {}; + } + + return load_info; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h new file mode 100644 index 0000000000..eb78e9f770 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h @@ -0,0 +1,49 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +namespace VideoCommon +{ +class CustomTextureData; + +// This class provides functionality to load +// specific data (like textures). Where this data +// is loaded from is implementation defined +class CustomAssetLibrary +{ +public: + // TODO: this should be std::chrono::system_clock::time_point to + // support any type of loader where the time isn't from the filesystem + // but there's no way to convert filesystem times to system times + // without 'clock_cast', once our builders catch up + // to support 'clock_cast' we should update this + // For now, it's fine as a filesystem library is all that is + // available + using TimeType = std::filesystem::file_time_type; + + // The AssetID is a unique identifier for a particular asset + using AssetID = std::string; + + struct LoadInfo + { + std::size_t m_bytes_loaded; + CustomAssetLibrary::TimeType m_load_time; + }; + + // Loads a texture, if there are no levels, bytes loaded will be empty + virtual LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* 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, CustomTextureData* data); +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 40c9cd3ded..d8e1825ad1 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -8,6 +8,10 @@ add_library(videocommon AbstractStagingTexture.h AbstractTexture.cpp AbstractTexture.h + Assets/CustomAsset.cpp + Assets/CustomAsset.h + Assets/CustomAssetLibrary.cpp + Assets/CustomAssetLibrary.h AsyncRequests.cpp AsyncRequests.h AsyncShaderCompiler.cpp