diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp index 8a78fed490..7b6420088f 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp @@ -6,7 +6,7 @@ #include #include "Common/Logging/Log.h" -#include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/Assets/TextureAsset.h" namespace VideoCommon { @@ -26,16 +26,26 @@ std::size_t GetAssetSize(const CustomTextureData& data) } } // namespace CustomAssetLibrary::LoadInfo CustomAssetLibrary::LoadGameTexture(const AssetID& asset_id, - CustomTextureData* data) + TextureData* 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 - for (std::size_t slice_index = 0; slice_index < data->m_slices.size(); slice_index++) + if (data->m_type != TextureData::Type::Type_Texture2D) { - auto& slice = data->m_slices[slice_index]; + 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). diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h index dba514f4ce..17b16f9fb9 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h @@ -10,9 +10,9 @@ namespace VideoCommon { -class CustomTextureData; struct MaterialData; struct PixelShaderData; +struct TextureData; // This class provides functionality to load // specific data (like textures). Where this data @@ -32,14 +32,14 @@ public: }; // Loads a texture, if there are no levels, bytes loaded will be empty - virtual LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) = 0; + 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, CustomTextureData* data); + LoadInfo LoadGameTexture(const AssetID& asset_id, TextureData* data); // Loads a pixel shader virtual LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) = 0; diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp index f579a5eb4b..134b18b496 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp @@ -77,13 +77,6 @@ void CustomAssetLoader ::Shutdown() m_total_bytes_loaded = 0; } -std::shared_ptr -CustomAssetLoader::LoadTexture(const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr library) -{ - return LoadOrCreateAsset(asset_id, m_textures, std::move(library)); -} - std::shared_ptr CustomAssetLoader::LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library) diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h index fe86a40835..920f62e830 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h @@ -38,9 +38,6 @@ public: // 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 LoadTexture(const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr library); - std::shared_ptr LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library); @@ -80,7 +77,6 @@ private: static constexpr auto TIME_BETWEEN_ASSET_MONITOR_CHECKS = std::chrono::milliseconds{500}; - std::map> m_textures; std::map> m_game_textures; std::map> m_pixel_shaders; std::map> m_materials; diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp index cbb3046f4d..9a749ecec9 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp @@ -12,6 +12,7 @@ #include "Common/StringUtil.h" #include "VideoCommon/Assets/MaterialAsset.h" #include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/Assets/TextureAsset.h" namespace VideoCommon { @@ -219,66 +220,126 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const As } CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id, - CustomTextureData* data) + TextureData* data) { const auto asset_map = GetAssetMapForID(asset_id); - // Raw texture is expected to have one asset mapped - if (asset_map.empty() || asset_map.size() > 1) + // Texture can optionally have a metadata file as well + if (asset_map.empty() || asset_map.size() > 2) { - ERROR_LOG_FMT(VIDEO, "Asset '{}' error - raw texture expected to have one file mapped!", + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - raw texture expected to have one or two files mapped!", asset_id); return {}; } - const auto& asset_path = asset_map.begin()->second; - std::error_code ec; - const auto last_loaded_time = std::filesystem::last_write_time(asset_path, ec); - if (ec) + const auto metadata = asset_map.find("metadata"); + const auto texture_path = asset_map.find("texture"); + + if (texture_path == asset_map.end()) { - ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to get last write time with error '{}'!", - asset_id, ec); + ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a texture entry mapped!", asset_id); return {}; } - auto ext = PathToString(asset_path.extension()); + + std::size_t metadata_size = 0; + if (metadata != asset_map.end()) + { + std::error_code ec; + metadata_size = std::filesystem::file_size(metadata->second, ec); + if (ec) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - failed to get texture metadata file size with error '{}'!", + asset_id, ec); + return {}; + } + + std::string json_data; + if (!File::ReadFileToString(PathToString(metadata->second), json_data)) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the json file '{}',", asset_id, + PathToString(metadata->second)); + return {}; + } + + picojson::value root; + const auto error = picojson::parse(root, json_data); + + if (!error.empty()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - failed to load the json file '{}', due to parse error: {}", + asset_id, PathToString(metadata->second), error); + return {}; + } + if (!root.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' error - failed to load the json file '{}', due to root not being an object!", + asset_id, PathToString(metadata->second)); + return {}; + } + + const auto& root_obj = root.get(); + if (!TextureData::FromJson(asset_id, root_obj, data)) + { + return {}; + } + } + else + { + data->m_type = TextureData::Type::Type_Texture2D; + } + + auto ext = PathToString(texture_path->second.extension()); Common::ToLower(&ext); if (ext == ".dds") { - if (!LoadDDSTexture(data, PathToString(asset_path))) + if (!LoadDDSTexture(&data->m_texture, PathToString(texture_path->second))) { ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load dds texture!", asset_id); return {}; } - if (data->m_slices.empty()) [[unlikely]] - data->m_slices.push_back({}); + if (data->m_texture.m_slices.empty()) [[unlikely]] + data->m_texture.m_slices.push_back({}); - if (!LoadMips(asset_path, &data->m_slices[0])) + if (!LoadMips(texture_path->second, &data->m_texture.m_slices[0])) return {}; - return LoadInfo{GetAssetSize(*data), FileTimeToSysTime(last_loaded_time)}; + return LoadInfo{GetAssetSize(data->m_texture) + metadata_size, GetLastAssetWriteTime(asset_id)}; } else if (ext == ".png") { - // If we have no slices, create one - if (data->m_slices.empty()) - data->m_slices.push_back({}); + // 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 {}; + } - auto& slice = data->m_slices[0]; + // 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(asset_path))) + 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(asset_path, &slice)) + if (!LoadMips(texture_path->second, &slice)) return {}; - return LoadInfo{GetAssetSize(*data), FileTimeToSysTime(last_loaded_time)}; + return LoadInfo{GetAssetSize(data->m_texture) + metadata_size, GetLastAssetWriteTime(asset_id)}; } ERROR_LOG_FMT(VIDEO, "Asset '{}' error - extension '{}' unknown!", asset_id, ext); diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h index 21c5ce7d54..244b49c88b 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h @@ -20,7 +20,7 @@ class DirectFilesystemAssetLibrary final : public CustomAssetLibrary public: using AssetMap = std::map; - LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override; + LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) override; LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) override; LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) override; diff --git a/Source/Core/VideoCommon/Assets/TextureAsset.cpp b/Source/Core/VideoCommon/Assets/TextureAsset.cpp index 8cd92aa89f..9418add8d1 100644 --- a/Source/Core/VideoCommon/Assets/TextureAsset.cpp +++ b/Source/Core/VideoCommon/Assets/TextureAsset.cpp @@ -97,20 +97,6 @@ bool ParseSampler(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, return true; } } // namespace -CustomAssetLibrary::LoadInfo RawTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) -{ - auto potential_data = std::make_shared(); - const auto loaded_info = m_owning_library->LoadTexture(asset_id, potential_data.get()); - if (loaded_info.m_bytes_loaded == 0) - return {}; - { - std::lock_guard lk(m_data_lock); - m_loaded = true; - m_data = std::move(potential_data); - } - return loaded_info; -} - bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, TextureData* data) { @@ -160,7 +146,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id, CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) { - auto potential_data = std::make_shared(); + auto potential_data = std::make_shared(); const auto loaded_info = m_owning_library->LoadGameTexture(asset_id, potential_data.get()); if (loaded_info.m_bytes_loaded == 0) return {}; @@ -184,7 +170,7 @@ bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const return false; } - if (m_data->m_slices.empty()) + if (m_data->m_texture.m_slices.empty()) { ERROR_LOG_FMT(VIDEO, "Game texture can't be validated for asset '{}' because no data was available.", @@ -192,7 +178,7 @@ bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const return false; } - if (m_data->m_slices.size() > 1) + if (m_data->m_texture.m_slices.size() > 1) { ERROR_LOG_FMT( VIDEO, @@ -201,7 +187,7 @@ bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const return false; } - const auto& slice = m_data->m_slices[0]; + const auto& slice = m_data->m_texture.m_slices[0]; if (slice.m_levels.empty()) { ERROR_LOG_FMT( diff --git a/Source/Core/VideoCommon/Assets/TextureAsset.h b/Source/Core/VideoCommon/Assets/TextureAsset.h index ef4341c83d..fc9e3166df 100644 --- a/Source/Core/VideoCommon/Assets/TextureAsset.h +++ b/Source/Core/VideoCommon/Assets/TextureAsset.h @@ -3,23 +3,16 @@ #pragma once +#include #include +#include "Common/EnumFormatter.h" #include "VideoCommon/Assets/CustomAsset.h" #include "VideoCommon/Assets/CustomTextureData.h" #include "VideoCommon/RenderState.h" namespace VideoCommon { -class RawTextureAsset final : public CustomLoadableAsset -{ -public: - using CustomLoadableAsset::CustomLoadableAsset; - -private: - CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; -}; - struct TextureData { static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, @@ -32,11 +25,11 @@ struct TextureData Type_Max = Type_TextureCube }; Type m_type; - CustomTextureData m_data; + CustomTextureData m_texture; SamplerState m_sampler; }; -class GameTextureAsset final : public CustomLoadableAsset +class GameTextureAsset final : public CustomLoadableAsset { public: using CustomLoadableAsset::CustomLoadableAsset; @@ -49,3 +42,10 @@ private: CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; }; } // namespace VideoCommon + +template <> +struct fmt::formatter + : EnumFormatter +{ + constexpr formatter() : EnumFormatter({"Undefined", "Texture2D", "TextureCube"}) {} +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp index 8f5225f142..267f88bb4c 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp @@ -423,7 +423,7 @@ void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate* auto data = game_texture.m_asset->GetData(); if (data) { - if (data->m_slices.empty() || data->m_slices[0].m_levels.empty()) + if (data->m_texture.m_slices.empty() || data->m_texture.m_slices[0].m_levels.empty()) { ERROR_LOG_FMT( VIDEO, @@ -431,15 +431,16 @@ void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate* create->texture_name, game_texture.m_asset->GetAssetId()); m_valid = false; } - else if (create->texture_width != data->m_slices[0].m_levels[0].width || - create->texture_height != data->m_slices[0].m_levels[0].height) + else if (create->texture_width != data->m_texture.m_slices[0].m_levels[0].width || + create->texture_height != data->m_texture.m_slices[0].m_levels[0].height) { ERROR_LOG_FMT(VIDEO, "Custom pipeline for texture '{}' has asset '{}' that does not match " "the width/height of the texture loaded. Texture {}x{} vs asset {}x{}", create->texture_name, game_texture.m_asset->GetAssetId(), create->texture_width, create->texture_height, - data->m_slices[0].m_levels[0].width, data->m_slices[0].m_levels[0].height); + data->m_texture.m_slices[0].m_levels[0].width, + data->m_texture.m_slices[0].m_levels[0].height); m_valid = false; } } diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index 6a489973a5..2f5fd93709 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -130,8 +130,8 @@ void HiresTexture::Update() { // Since this is just a texture (single file) the mapper doesn't really matter // just provide a string - s_file_library->SetAssetIDMapData( - filename, std::map{{"", StringToPath(path)}}); + s_file_library->SetAssetIDMapData(filename, std::map{ + {"texture", StringToPath(path)}}); if (g_ActiveConfig.bCacheHiresTextures) { diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index fa677bf1b5..50464cce5f 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -1606,7 +1606,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp } std::vector> cached_game_assets; - std::vector> data_for_assets; + std::vector data_for_assets; bool has_arbitrary_mipmaps = false; bool skip_texture_dump = false; std::shared_ptr hires_texture; @@ -1640,12 +1640,12 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp auto data = asset->GetData(); if (data) { - if (!data->m_slices.empty()) + if (!data->m_texture.m_slices.empty()) { - if (!data->m_slices[0].m_levels.empty()) + if (!data->m_texture.m_slices[0].m_levels.empty()) { - height = data->m_slices[0].m_levels[0].height; - width = data->m_slices[0].m_levels[0].width; + height = data->m_texture.m_slices[0].m_levels[0].height; + width = data->m_texture.m_slices[0].m_levels[0].width; } } } @@ -1667,7 +1667,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp { if (cached_asset.m_asset->Validate(texture_info.GetRawWidth(), texture_info.GetRawHeight())) { - data_for_assets.push_back(std::move(data)); + data_for_assets.push_back(&data->m_texture); } } } @@ -1687,8 +1687,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp // expected because each texture is loaded into a texture array RcTcacheEntry TextureCacheBase::CreateTextureEntry( const TextureCreationInfo& creation_info, const TextureInfo& texture_info, - const int safety_color_sample_size, - std::vector> assets_data, + const int safety_color_sample_size, std::vector assets_data, const bool custom_arbitrary_mipmaps, bool skip_texture_dump) { #ifdef __APPLE__ @@ -1705,7 +1704,7 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry( assets_data.begin(), assets_data.end(), [](const auto& lhs, const auto& rhs) { return lhs->m_slices[0].m_levels.size() < rhs->m_slices[0].m_levels.size(); }); - return max_element->get()->m_slices[0].m_levels.size(); + return (*max_element)->m_slices[0].m_levels.size(); }; const u32 texLevels = no_mips ? 1 : (u32)calculate_max_levels(); const auto& first_level = assets_data[0]->m_slices[0].m_levels[0]; diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index 7c8b74fb5e..0271677962 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -346,11 +346,10 @@ private: void SetBackupConfig(const VideoConfig& config); - RcTcacheEntry - CreateTextureEntry(const TextureCreationInfo& creation_info, const TextureInfo& texture_info, - int safety_color_sample_size, - std::vector> assets_data, - bool custom_arbitrary_mipmaps, bool skip_texture_dump); + RcTcacheEntry CreateTextureEntry(const TextureCreationInfo& creation_info, + const TextureInfo& texture_info, int safety_color_sample_size, + std::vector assets_data, + bool custom_arbitrary_mipmaps, bool skip_texture_dump); RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride); diff --git a/docs/CustomPipelineGraphicsMod.md b/docs/CustomPipelineGraphicsMod.md index 771d3dba36..42d6a1e9b7 100644 --- a/docs/CustomPipelineGraphicsMod.md +++ b/docs/CustomPipelineGraphicsMod.md @@ -44,7 +44,7 @@ A full example is given below: "name": "normal_texture", "data": { - "": "normal_texture.png" + "texture": "normal_texture.png" } } ],