diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index ddb2cd86e7ee..6facc4c074cc 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -659,6 +659,7 @@ + @@ -1260,10 +1261,10 @@ + - diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 2737d5a514fb..40c9cd3ded7c 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -65,6 +65,8 @@ add_library(videocommon GraphicsModSystem/Runtime/Actions/ScaleAction.h GraphicsModSystem/Runtime/Actions/SkipAction.cpp GraphicsModSystem/Runtime/Actions/SkipAction.h + GraphicsModSystem/Runtime/CustomTextureData.cpp + GraphicsModSystem/Runtime/CustomTextureData.h GraphicsModSystem/Runtime/FBInfo.cpp GraphicsModSystem/Runtime/FBInfo.h GraphicsModSystem/Runtime/GraphicsModAction.h @@ -75,7 +77,6 @@ add_library(videocommon GraphicsModSystem/Runtime/GraphicsModManager.h HiresTextures.cpp HiresTextures.h - HiresTextures_DDSLoader.cpp IndexGenerator.cpp IndexGenerator.h LightingShaderGen.cpp diff --git a/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.cpp similarity index 85% rename from Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp rename to Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.cpp index f3b2daf5eb0c..10b84b4a7a81 100644 --- a/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.cpp @@ -1,7 +1,7 @@ -// Copyright 2017 Dolphin Emulator Project +// Copyright 2023 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "VideoCommon/HiresTextures.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.h" #include #include @@ -11,6 +11,7 @@ #include "Common/Align.h" #include "Common/IOFile.h" +#include "Common/Image.h" #include "Common/Logging/Log.h" #include "Common/Swap.h" #include "VideoCommon/VideoConfig.h" @@ -127,7 +128,7 @@ constexpr DDS_PIXELFORMAT DDSPF_R8G8B8 = { // End of Microsoft code from DDS.h. -bool DDSPixelFormatMatches(const DDS_PIXELFORMAT& pf1, const DDS_PIXELFORMAT& pf2) +static constexpr bool DDSPixelFormatMatches(const DDS_PIXELFORMAT& pf1, const DDS_PIXELFORMAT& pf2) { return std::tie(pf1.dwSize, pf1.dwFlags, pf1.dwFourCC, pf1.dwRGBBitCount, pf1.dwRBitMask, pf1.dwGBitMask, pf1.dwGBitMask, pf1.dwBBitMask, pf1.dwABitMask) == @@ -147,15 +148,30 @@ struct DDSLoadInfo size_t first_mip_size = 0; u32 first_mip_row_length = 0; - std::function conversion_function; + std::function conversion_function; }; -u32 GetBlockCount(u32 extent, u32 block_size) +static constexpr u32 GetBlockCount(u32 extent, u32 block_size) { return std::max(Common::AlignUp(extent, block_size) / block_size, 1u); } -void ConvertTexture_X8B8G8R8(HiresTexture::Level* level) +static u32 CalculateMipCount(u32 width, u32 height) +{ + u32 mip_width = width; + u32 mip_height = height; + u32 mip_count = 1; + while (mip_width > 1 || mip_height > 1) + { + mip_width = std::max(mip_width / 2, 1u); + mip_height = std::max(mip_height / 2, 1u); + mip_count++; + } + + return mip_count; +} + +static void ConvertTexture_X8B8G8R8(VideoCommon::CustomTextureData::Level* level) { u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) @@ -169,7 +185,7 @@ void ConvertTexture_X8B8G8R8(HiresTexture::Level* level) } } -void ConvertTexture_A8R8G8B8(HiresTexture::Level* level) +static void ConvertTexture_A8R8G8B8(VideoCommon::CustomTextureData::Level* level) { u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) @@ -186,7 +202,7 @@ void ConvertTexture_A8R8G8B8(HiresTexture::Level* level) } } -void ConvertTexture_X8R8G8B8(HiresTexture::Level* level) +static void ConvertTexture_X8R8G8B8(VideoCommon::CustomTextureData::Level* level) { u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) @@ -203,7 +219,7 @@ void ConvertTexture_X8R8G8B8(HiresTexture::Level* level) } } -void ConvertTexture_R8G8B8(HiresTexture::Level* level) +static void ConvertTexture_R8G8B8(VideoCommon::CustomTextureData::Level* level) { std::vector new_data(level->row_length * level->height * sizeof(u32)); @@ -227,7 +243,7 @@ void ConvertTexture_R8G8B8(HiresTexture::Level* level) level->data = std::move(new_data); } -bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) +static bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) { // Exit as early as possible for non-DDS textures, since all extensions are currently // passed through this function. @@ -261,7 +277,7 @@ bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) if (header.dwMipMapCount != 0) info->mip_count = header.dwMipMapCount; else - info->mip_count = HiresTexture::CalculateMipCount(info->width, info->height); + info->mip_count = CalculateMipCount(info->width, info->height); } else { @@ -400,9 +416,9 @@ bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) return true; } -bool ReadMipLevel(HiresTexture::Level* level, File::IOFile& file, const std::string& filename, - u32 mip_level, const DDSLoadInfo& info, u32 width, u32 height, u32 row_length, - size_t size) +static bool ReadMipLevel(VideoCommon::CustomTextureData::Level* level, File::IOFile& file, + const std::string& filename, u32 mip_level, const DDSLoadInfo& info, + u32 width, u32 height, u32 row_length, size_t size) { // D3D11 cannot handle block compressed textures where the first mip level is // not a multiple of the block size. @@ -434,7 +450,9 @@ bool ReadMipLevel(HiresTexture::Level* level, File::IOFile& file, const std::str } // namespace -bool HiresTexture::LoadDDSTexture(HiresTexture* tex, const std::string& filename) +namespace VideoCommon +{ +bool LoadDDSTexture(CustomTextureData* texture, const std::string& filename) { File::IOFile file; file.Open(filename, "rb"); @@ -446,7 +464,7 @@ bool HiresTexture::LoadDDSTexture(HiresTexture* tex, const std::string& filename return false; // Read first mip level, as it may have a custom pitch. - Level first_level; + CustomTextureData::Level first_level; if (!file.Seek(info.first_mip_offset, File::SeekOrigin::Begin) || !ReadMipLevel(&first_level, file, filename, 0, info, info.width, info.height, info.first_mip_row_length, info.first_mip_size)) @@ -454,7 +472,7 @@ bool HiresTexture::LoadDDSTexture(HiresTexture* tex, const std::string& filename return false; } - tex->m_levels.push_back(std::move(first_level)); + texture->m_levels.push_back(std::move(first_level)); // Read in any remaining mip levels in the file. // If the .dds file does not contain a full mip chain, we'll fall back to the old path. @@ -470,18 +488,18 @@ bool HiresTexture::LoadDDSTexture(HiresTexture* tex, const std::string& filename u32 blocks_high = GetBlockCount(mip_height, info.block_size); u32 mip_row_length = blocks_wide * info.block_size; size_t mip_size = blocks_wide * static_cast(info.bytes_per_block) * blocks_high; - Level level; + CustomTextureData::Level level; if (!ReadMipLevel(&level, file, filename, i, info, mip_width, mip_height, mip_row_length, mip_size)) break; - tex->m_levels.push_back(std::move(level)); + texture->m_levels.push_back(std::move(level)); } return true; } -bool HiresTexture::LoadDDSTexture(Level& level, const std::string& filename, u32 mip_level) +bool LoadDDSTexture(CustomTextureData::Level* level, const std::string& filename, u32 mip_level) { // Only loading a single mip level. File::IOFile file; @@ -493,6 +511,34 @@ bool HiresTexture::LoadDDSTexture(Level& level, const std::string& filename, u32 if (!ParseDDSHeader(file, &info)) return false; - return ReadMipLevel(&level, file, filename, mip_level, info, info.width, info.height, + return ReadMipLevel(level, file, filename, mip_level, info, info.width, info.height, info.first_mip_row_length, info.first_mip_size); } + +bool LoadPNGTexture(CustomTextureData* texture, const std::string& filename) +{ + return LoadPNGTexture(&texture->m_levels[0], filename); +} + +bool LoadPNGTexture(CustomTextureData::Level* level, const std::string& filename) +{ + if (!level) [[unlikely]] + return false; + + File::IOFile file; + file.Open(filename, "rb"); + std::vector buffer(file.GetSize()); + file.ReadBytes(buffer.data(), file.GetSize()); + + if (!Common::LoadPNG(buffer, &level->data, &level->width, &level->height)) + return false; + + if (level->data.empty()) + return false; + + // Loaded PNG images are converted to RGBA. + level->format = AbstractTextureFormat::RGBA8; + level->row_length = level->width; + return true; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.h new file mode 100644 index 000000000000..9a16bf1db089 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.h @@ -0,0 +1,32 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/TextureConfig.h" + +namespace VideoCommon +{ +class CustomTextureData +{ +public: + struct Level + { + std::vector data; + AbstractTextureFormat format = AbstractTextureFormat::RGBA8; + u32 width = 0; + u32 height = 0; + u32 row_length = 0; + }; + std::vector m_levels; +}; + +bool LoadDDSTexture(CustomTextureData* texture, const std::string& filename); +bool LoadDDSTexture(CustomTextureData::Level* level, const std::string& filename, u32 mip_level); +bool LoadPNGTexture(CustomTextureData* texture, const std::string& filename); +bool LoadPNGTexture(CustomTextureData::Level* level, const std::string& filename); +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index f3d6d7737305..0227105d1919 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -185,7 +185,7 @@ void HiresTexture::Prefetch() } if (iter != s_textureCache.end()) { - for (const Level& l : iter->second->m_levels) + for (const VideoCommon::CustomTextureData::Level& l : iter->second->m_data.m_levels) size_sum += l.data.size(); } } @@ -246,21 +246,6 @@ std::string HiresTexture::GenBaseName(const TextureInfo& texture_info, bool dump return ""; } -u32 HiresTexture::CalculateMipCount(u32 width, u32 height) -{ - u32 mip_width = width; - u32 mip_height = height; - u32 mip_count = 1; - while (mip_width > 1 || mip_height > 1) - { - mip_width = std::max(mip_width / 2, 1u); - mip_height = std::max(mip_height / 2, 1u); - mip_count++; - } - - return mip_count; -} - std::shared_ptr HiresTexture::Search(const TextureInfo& texture_info) { const std::string base_filename = GenBaseName(texture_info); @@ -298,10 +283,10 @@ std::unique_ptr HiresTexture::Load(const std::string& base_filenam std::unique_ptr ret = std::unique_ptr(new HiresTexture()); const DiskTexture& first_mip_file = filename_iter->second; ret->m_has_arbitrary_mipmaps = first_mip_file.has_arbitrary_mipmaps; - LoadDDSTexture(ret.get(), first_mip_file.path); + VideoCommon::LoadDDSTexture(&ret->m_data, first_mip_file.path); // Load remaining mip levels, or from the start if it's not a DDS texture. - for (u32 mip_level = static_cast(ret->m_levels.size());; mip_level++) + for (u32 mip_level = static_cast(ret->m_data.m_levels.size());; mip_level++) { std::string filename = base_filename; if (mip_level != 0) @@ -313,30 +298,25 @@ std::unique_ptr HiresTexture::Load(const std::string& base_filenam // Try loading DDS textures first, that way we maintain compression of DXT formats. // TODO: Reduce the number of open() calls here. We could use one fd. - Level level; - if (!LoadDDSTexture(level, filename_iter->second.path, mip_level)) + VideoCommon::CustomTextureData::Level level; + if (!LoadDDSTexture(&level, filename_iter->second.path, mip_level)) { - File::IOFile file; - file.Open(filename_iter->second.path, "rb"); - std::vector buffer(file.GetSize()); - file.ReadBytes(buffer.data(), file.GetSize()); - - if (!LoadTexture(level, buffer)) + if (!LoadPNGTexture(&level, filename_iter->second.path)) { ERROR_LOG_FMT(VIDEO, "Custom texture {} failed to load", filename); break; } } - ret->m_levels.push_back(std::move(level)); + ret->m_data.m_levels.push_back(std::move(level)); } // If we failed to load any mip levels, we can't use this texture at all. - if (ret->m_levels.empty()) + if (ret->m_data.m_levels.empty()) return nullptr; // Verify that the aspect ratio of the texture hasn't changed, as this could have side-effects. - const Level& first_mip = ret->m_levels[0]; + const VideoCommon::CustomTextureData::Level& first_mip = ret->m_data.m_levels[0]; if (first_mip.width * height != first_mip.height * width) { ERROR_LOG_FMT(VIDEO, @@ -357,14 +337,14 @@ std::unique_ptr HiresTexture::Load(const std::string& base_filenam // 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(ret->m_levels.size()); mip_level++) + for (u32 mip_level = 1; mip_level < static_cast(ret->m_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 Level& level = ret->m_levels[mip_level]; + const VideoCommon::CustomTextureData::Level& level = ret->m_data.m_levels[mip_level]; if (current_mip_width == level.width && current_mip_height == level.height) continue; @@ -381,13 +361,15 @@ std::unique_ptr HiresTexture::Load(const std::string& base_filenam } // Drop this mip level and any others after it. - while (ret->m_levels.size() > mip_level) - ret->m_levels.pop_back(); + while (ret->m_data.m_levels.size() > mip_level) + ret->m_data.m_levels.pop_back(); } // All levels have to have the same format. - if (std::any_of(ret->m_levels.begin(), ret->m_levels.end(), - [&ret](const Level& l) { return l.format != ret->m_levels[0].format; })) + if (std::any_of(ret->m_data.m_levels.begin(), ret->m_data.m_levels.end(), + [&ret](const VideoCommon::CustomTextureData::Level& l) { + return l.format != ret->m_data.m_levels[0].format; + })) { ERROR_LOG_FMT(VIDEO, "Custom texture {} has inconsistent formats across mip levels.", first_mip_file.path); @@ -398,20 +380,6 @@ std::unique_ptr HiresTexture::Load(const std::string& base_filenam return ret; } -bool HiresTexture::LoadTexture(Level& level, const std::vector& buffer) -{ - if (!Common::LoadPNG(buffer, &level.data, &level.width, &level.height)) - return false; - - if (level.data.empty()) - return false; - - // Loaded PNG images are converted to RGBA. - level.format = AbstractTextureFormat::RGBA8; - level.row_length = level.width; - return true; -} - std::set GetTextureDirectoriesWithGameId(const std::string& root_directory, const std::string& game_id) { @@ -464,7 +432,7 @@ HiresTexture::~HiresTexture() AbstractTextureFormat HiresTexture::GetFormat() const { - return m_levels.at(0).format; + return m_data.m_levels.at(0).format; } bool HiresTexture::HasArbitraryMipmaps() const diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h index bcd889e18c45..2381d24bd6e7 100644 --- a/Source/Core/VideoCommon/HiresTextures.h +++ b/Source/Core/VideoCommon/HiresTextures.h @@ -9,6 +9,7 @@ #include #include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureData.h" #include "VideoCommon/TextureConfig.h" #include "VideoCommon/TextureInfo.h" @@ -29,31 +30,21 @@ class HiresTexture static std::string GenBaseName(const TextureInfo& texture_info, bool dump = false); - static u32 CalculateMipCount(u32 width, u32 height); - ~HiresTexture(); AbstractTextureFormat GetFormat() const; bool HasArbitraryMipmaps() const; - struct Level - { - std::vector data; - AbstractTextureFormat format = AbstractTextureFormat::RGBA8; - u32 width = 0; - u32 height = 0; - u32 row_length = 0; - }; - std::vector m_levels; + VideoCommon::CustomTextureData& GetData() { return m_data; } + const VideoCommon::CustomTextureData& GetData() const { return m_data; } private: static std::unique_ptr Load(const std::string& base_filename, u32 width, u32 height); - static bool LoadDDSTexture(HiresTexture* tex, const std::string& filename); - static bool LoadDDSTexture(Level& level, const std::string& filename, u32 mip_level); - static bool LoadTexture(Level& level, const std::vector& buffer); static void Prefetch(); HiresTexture() = default; + + VideoCommon::CustomTextureData m_data; bool m_has_arbitrary_mipmaps = false; }; diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 8b5e56af3707..6d138d5912e7 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -1594,7 +1594,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp if (hires_tex) { - const auto& level = hires_tex->m_levels[0]; + const auto& level = hires_tex->GetData().m_levels[0]; if (level.width != width || level.height != height) { width = level.width; @@ -1612,7 +1612,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp #endif // how many levels the allocated texture shall have const u32 texLevels = no_mips ? 1 : - hires_tex ? (u32)hires_tex->m_levels.size() : + hires_tex ? (u32)hires_tex->GetData().m_levels.size() : texture_info.GetLevelCount(); // We can decode on the GPU if it is a supported format and the flag is enabled. @@ -1634,7 +1634,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp ArbitraryMipmapDetector arbitrary_mip_detector; if (hires_tex) { - const auto& level = hires_tex->m_levels[0]; + const auto& level = hires_tex->GetData().m_levels[0]; entry->texture->Load(0, level.width, level.height, level.row_length, level.data.data(), level.data.size()); } @@ -1719,7 +1719,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp { for (u32 level_index = 1; level_index != texLevels; ++level_index) { - const auto& level = hires_tex->m_levels[level_index]; + const auto& level = hires_tex->GetData().m_levels[level_index]; entry->texture->Load(level_index, level.width, level.height, level.row_length, level.data.data(), level.data.size()); }