mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-13 10:47:48 +00:00
VideoCommon: rename GameTextureAsset into TextureAsset and make it only contain CustomTextureData. Move validation and load logic to individual functions
This commit is contained in:
parent
2ae43324cb
commit
d8ea31ca46
@ -675,6 +675,7 @@
|
||||
<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\AsyncRequests.h" />
|
||||
<ClInclude Include="VideoCommon\AsyncShaderCompiler.h" />
|
||||
@ -1322,13 +1323,13 @@
|
||||
<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\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" />
|
||||
|
@ -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
|
||||
@ -31,12 +32,11 @@ public:
|
||||
|
||||
virtual ~CustomAssetLibrary() = default;
|
||||
|
||||
// Loads a texture, if there are no levels, bytes loaded will be empty
|
||||
virtual LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) = 0;
|
||||
// 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 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);
|
||||
// Loads a texture, if there are no levels, bytes loaded will be empty
|
||||
virtual LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) = 0;
|
||||
|
||||
// Loads a pixel shader
|
||||
virtual LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) = 0;
|
||||
|
@ -17,6 +17,7 @@
|
||||
#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
|
||||
@ -277,7 +278,37 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMesh(const AssetI
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -330,7 +361,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 {};
|
||||
}
|
||||
@ -338,61 +369,15 @@ 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};
|
||||
}
|
||||
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};
|
||||
}
|
||||
|
||||
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,
|
||||
@ -402,58 +387,6 @@ void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id,
|
||||
m_asset_id_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++)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VideoCommon::Assets::AssetMap
|
||||
DirectFilesystemAssetLibrary::GetAssetMapForID(const AssetID& asset_id) const
|
||||
{
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/Assets/Types.h"
|
||||
|
||||
namespace VideoCommon
|
||||
@ -19,7 +20,8 @@ namespace VideoCommon
|
||||
class DirectFilesystemAssetLibrary final : public CustomAssetLibrary
|
||||
{
|
||||
public:
|
||||
LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) override;
|
||||
LoadInfo LoadTexture(const AssetID& asset_id, TextureAndSamplerData* data) override;
|
||||
LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override;
|
||||
LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) override;
|
||||
LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) override;
|
||||
LoadInfo LoadMesh(const AssetID& asset_id, MeshData* data) override;
|
||||
@ -30,9 +32,6 @@ public:
|
||||
void SetAssetIDMapData(const AssetID& asset_id, Assets::AssetMap asset_path_map);
|
||||
|
||||
private:
|
||||
// Loads additional mip levels into the texture structure until _mip<N> texture is not found
|
||||
bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData::ArraySlice* data);
|
||||
|
||||
// Gets the asset map given an asset id
|
||||
Assets::AssetMap GetAssetMapForID(const AssetID& asset_id) const;
|
||||
|
||||
|
@ -153,8 +153,8 @@ bool ParseSampler(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
const picojson::object& json, TextureData* data)
|
||||
bool TextureAndSamplerData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
const picojson::object& json, TextureAndSamplerData* data)
|
||||
{
|
||||
const auto type_iter = json.find("type");
|
||||
if (type_iter == json.end())
|
||||
@ -176,7 +176,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
|
||||
if (type == "texture2d")
|
||||
{
|
||||
data->m_type = TextureData::Type::Type_Texture2D;
|
||||
data->m_type = TextureAndSamplerData::Type::Type_Texture2D;
|
||||
|
||||
if (!ParseSampler(asset_id, json, &data->m_sampler))
|
||||
{
|
||||
@ -185,7 +185,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
}
|
||||
else if (type == "texturecube")
|
||||
{
|
||||
data->m_type = TextureData::Type::Type_TextureCube;
|
||||
data->m_type = TextureAndSamplerData::Type::Type_TextureCube;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -199,7 +199,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureData::ToJson(picojson::object* obj, const TextureData& data)
|
||||
void TextureAndSamplerData::ToJson(picojson::object* obj, const TextureAndSamplerData& data)
|
||||
{
|
||||
if (!obj) [[unlikely]]
|
||||
return;
|
||||
@ -207,13 +207,13 @@ void TextureData::ToJson(picojson::object* obj, const TextureData& data)
|
||||
auto& json_obj = *obj;
|
||||
switch (data.m_type)
|
||||
{
|
||||
case TextureData::Type::Type_Texture2D:
|
||||
case TextureAndSamplerData::Type::Type_Texture2D:
|
||||
json_obj.emplace("type", "texture2d");
|
||||
break;
|
||||
case TextureData::Type::Type_TextureCube:
|
||||
case TextureAndSamplerData::Type::Type_TextureCube:
|
||||
json_obj.emplace("type", "texturecube");
|
||||
break;
|
||||
case TextureData::Type::Type_Undefined:
|
||||
case TextureAndSamplerData::Type::Type_Undefined:
|
||||
break;
|
||||
};
|
||||
|
||||
@ -254,10 +254,10 @@ void TextureData::ToJson(picojson::object* obj, const TextureData& data)
|
||||
json_obj.emplace("filter_mode", filter_mode);
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
|
||||
CustomAssetLibrary::LoadInfo TextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
|
||||
{
|
||||
auto potential_data = std::make_shared<TextureData>();
|
||||
const auto loaded_info = m_owning_library->LoadGameTexture(asset_id, potential_data.get());
|
||||
auto potential_data = std::make_shared<CustomTextureData>();
|
||||
const auto loaded_info = m_owning_library->LoadTexture(asset_id, potential_data.get());
|
||||
if (loaded_info.m_bytes_loaded == 0)
|
||||
return {};
|
||||
{
|
||||
@ -267,75 +267,4 @@ CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary
|
||||
}
|
||||
return loaded_info;
|
||||
}
|
||||
|
||||
bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const
|
||||
{
|
||||
std::lock_guard lk(m_data_lock);
|
||||
|
||||
if (!m_loaded)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Game texture can't be validated for asset '{}' because it is not loaded yet.",
|
||||
GetAssetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_data->m_texture.m_slices.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Game texture can't be validated for asset '{}' because no data was available.",
|
||||
GetAssetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_data->m_texture.m_slices.size() > 1)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Game texture can't be validated for asset '{}' because it has more slices than expected.",
|
||||
GetAssetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& slice = m_data->m_texture.m_slices[0];
|
||||
if (slice.m_levels.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Game texture can't be validated for asset '{}' because first slice has no data available.",
|
||||
GetAssetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the aspect ratio of the texture hasn't changed, as this could have
|
||||
// side-effects.
|
||||
const VideoCommon::CustomTextureData::ArraySlice::Level& first_mip = slice.m_levels[0];
|
||||
if (first_mip.width * native_height != first_mip.height * native_width)
|
||||
{
|
||||
// Note: this feels like this should return an error but
|
||||
// for legacy reasons this is only a notice that something *could*
|
||||
// go wrong
|
||||
WARN_LOG_FMT(
|
||||
VIDEO,
|
||||
"Invalid custom texture size {}x{} for game texture asset '{}'. The aspect differs "
|
||||
"from the native size {}x{}.",
|
||||
first_mip.width, first_mip.height, GetAssetId(), native_width, native_height);
|
||||
}
|
||||
|
||||
// Same deal if the custom texture isn't a multiple of the native size.
|
||||
if (native_width != 0 && native_height != 0 &&
|
||||
(first_mip.width % native_width || first_mip.height % native_height))
|
||||
{
|
||||
// Note: this feels like this should return an error but
|
||||
// for legacy reasons this is only a notice that something *could*
|
||||
// go wrong
|
||||
WARN_LOG_FMT(
|
||||
VIDEO,
|
||||
"Invalid custom texture size {}x{} for game texture asset '{}'. Please use an integer "
|
||||
"upscaling factor based on the native size {}x{}.",
|
||||
first_mip.width, first_mip.height, GetAssetId(), native_width, native_height);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace VideoCommon
|
||||
|
@ -13,11 +13,11 @@
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
struct TextureData
|
||||
struct TextureAndSamplerData
|
||||
{
|
||||
static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json,
|
||||
TextureData* data);
|
||||
static void ToJson(picojson::object* obj, const TextureData& data);
|
||||
TextureAndSamplerData* data);
|
||||
static void ToJson(picojson::object* obj, const TextureAndSamplerData& data);
|
||||
enum class Type
|
||||
{
|
||||
Type_Undefined,
|
||||
@ -30,23 +30,19 @@ struct TextureData
|
||||
SamplerState m_sampler;
|
||||
};
|
||||
|
||||
class GameTextureAsset final : public CustomLoadableAsset<TextureData>
|
||||
class TextureAsset final : public CustomLoadableAsset<CustomTextureData>
|
||||
{
|
||||
public:
|
||||
using CustomLoadableAsset::CustomLoadableAsset;
|
||||
|
||||
// Validates that the game texture matches the native dimensions provided
|
||||
// Callees are expected to call this once the data is loaded
|
||||
bool Validate(u32 native_width, u32 native_height) const;
|
||||
|
||||
private:
|
||||
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
|
||||
};
|
||||
} // namespace VideoCommon
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<VideoCommon::TextureData::Type>
|
||||
: EnumFormatter<VideoCommon::TextureData::Type::Type_Max>
|
||||
struct fmt::formatter<VideoCommon::TextureAndSamplerData::Type>
|
||||
: EnumFormatter<VideoCommon::TextureAndSamplerData::Type::Type_Max>
|
||||
{
|
||||
constexpr formatter() : EnumFormatter({"Undefined", "Texture2D", "TextureCube"}) {}
|
||||
};
|
||||
|
244
Source/Core/VideoCommon/Assets/TextureAssetUtils.cpp
Normal file
244
Source/Core/VideoCommon/Assets/TextureAssetUtils.cpp
Normal file
@ -0,0 +1,244 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/TextureAssetUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Loads additional mip levels into the texture structure until _mip<N> texture is not found
|
||||
bool 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++)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
bool LoadTextureDataFromFile(const CustomAssetLibrary::AssetID& asset_id,
|
||||
const std::filesystem::path& asset_path,
|
||||
TextureAndSamplerData::Type type, CustomTextureData* data)
|
||||
{
|
||||
auto ext = PathToString(asset_path.extension());
|
||||
Common::ToLower(&ext);
|
||||
if (ext == ".dds")
|
||||
{
|
||||
if (!LoadDDSTexture(data, PathToString(asset_path)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load dds texture!", asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->m_slices.empty()) [[unlikely]]
|
||||
data->m_slices.emplace_back();
|
||||
|
||||
if (!LoadMips(asset_path, data->m_slices.data()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ext == ".png")
|
||||
{
|
||||
// PNG could support more complicated texture types in the future
|
||||
// but for now just error
|
||||
if (type != TextureAndSamplerData::Type::Type_Texture2D)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - PNG is not supported for texture type '{}'!",
|
||||
asset_id, type);
|
||||
return {};
|
||||
}
|
||||
|
||||
// If we have no slices, create one
|
||||
if (data->m_slices.empty())
|
||||
data->m_slices.emplace_back();
|
||||
|
||||
auto& slice = data->m_slices[0];
|
||||
// If we have no levels, create one to pass into LoadPNGTexture
|
||||
if (slice.m_levels.empty())
|
||||
slice.m_levels.emplace_back();
|
||||
|
||||
if (!LoadPNGTexture(slice.m_levels.data(), PathToString(asset_path)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load png texture!", asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadMips(asset_path, &slice))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - extension '{}' unknown!", asset_id, ext);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidateTextureData(const CustomAssetLibrary::AssetID& asset_id, const CustomTextureData& data,
|
||||
u32 native_width, u32 native_height)
|
||||
{
|
||||
if (data.m_slices.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Texture data can't be validated for asset '{}' because no data was available.",
|
||||
asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.m_slices.size() > 1)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Texture data can't be validated for asset '{}' because it has more slices than expected.",
|
||||
asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& slice = data.m_slices[0];
|
||||
if (slice.m_levels.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Texture data can't be validated for asset '{}' because first slice has no data available.",
|
||||
asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the aspect ratio of the texture hasn't changed, as this could have
|
||||
// side-effects.
|
||||
const CustomTextureData::ArraySlice::Level& first_mip = slice.m_levels[0];
|
||||
if (first_mip.width * native_height != first_mip.height * native_width)
|
||||
{
|
||||
// Note: this feels like this should return an error but
|
||||
// for legacy reasons this is only a notice that something *could*
|
||||
// go wrong
|
||||
WARN_LOG_FMT(VIDEO,
|
||||
"Invalid texture data size {}x{} for asset '{}'. The aspect differs "
|
||||
"from the native size {}x{}.",
|
||||
first_mip.width, first_mip.height, asset_id, native_width, native_height);
|
||||
}
|
||||
|
||||
// Same deal if the custom texture isn't a multiple of the native size.
|
||||
if (native_width != 0 && native_height != 0 &&
|
||||
(first_mip.width % native_width || first_mip.height % native_height))
|
||||
{
|
||||
// Note: this feels like this should return an error but
|
||||
// for legacy reasons this is only a notice that something *could*
|
||||
// go wrong
|
||||
WARN_LOG_FMT(VIDEO,
|
||||
"Invalid texture data size {}x{} for asset '{}'. Please use an integer "
|
||||
"upscaling factor based on the native size {}x{}.",
|
||||
first_mip.width, first_mip.height, asset_id, native_width, native_height);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PurgeInvalidMipsFromTextureData(const CustomAssetLibrary::AssetID& asset_id,
|
||||
CustomTextureData* data)
|
||||
{
|
||||
for (std::size_t slice_index = 0; slice_index < data->m_slices.size(); slice_index++)
|
||||
{
|
||||
auto& slice = data->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 false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace VideoCommon
|
22
Source/Core/VideoCommon/Assets/TextureAssetUtils.h
Normal file
22
Source/Core/VideoCommon/Assets/TextureAssetUtils.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
bool LoadTextureDataFromFile(const CustomAssetLibrary::AssetID& asset_id,
|
||||
const std::filesystem::path& asset_path,
|
||||
TextureAndSamplerData::Type type, CustomTextureData* data);
|
||||
|
||||
bool ValidateTextureData(const CustomAssetLibrary::AssetID& asset_id, const CustomTextureData& data,
|
||||
u32 native_width, u32 native_height);
|
||||
|
||||
bool PurgeInvalidMipsFromTextureData(const CustomAssetLibrary::AssetID& asset_id,
|
||||
CustomTextureData* data);
|
||||
} // namespace VideoCommon
|
@ -10,7 +10,6 @@ add_library(videocommon
|
||||
AbstractTexture.h
|
||||
Assets/CustomAsset.cpp
|
||||
Assets/CustomAsset.h
|
||||
Assets/CustomAssetLibrary.cpp
|
||||
Assets/CustomAssetLibrary.h
|
||||
Assets/CustomAssetLoader.cpp
|
||||
Assets/CustomAssetLoader.h
|
||||
@ -26,6 +25,8 @@ add_library(videocommon
|
||||
Assets/ShaderAsset.h
|
||||
Assets/TextureAsset.cpp
|
||||
Assets/TextureAsset.h
|
||||
Assets/TextureAssetUtils.cpp
|
||||
Assets/TextureAssetUtils.h
|
||||
Assets/Types.h
|
||||
AsyncRequests.cpp
|
||||
AsyncRequests.h
|
||||
|
@ -28,7 +28,7 @@ struct CustomPipeline
|
||||
|
||||
struct CachedTextureAsset
|
||||
{
|
||||
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset> m_cached_asset;
|
||||
VideoCommon::CachedAsset<VideoCommon::TextureAsset> m_cached_asset;
|
||||
std::unique_ptr<AbstractTexture> m_texture;
|
||||
std::string m_sampler_code;
|
||||
std::string m_define_code;
|
||||
|
@ -47,7 +47,7 @@ struct TextureCreate
|
||||
std::string_view texture_name;
|
||||
u32 texture_width;
|
||||
u32 texture_height;
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>>* custom_textures;
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::TextureAsset>>* custom_textures;
|
||||
|
||||
// Dependencies needed to reload the texture and trigger this create again
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::CustomAsset>>* additional_dependencies;
|
||||
|
Loading…
Reference in New Issue
Block a user