diff options
| author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | 2016-12-27 16:32:32 +0100 |
|---|---|---|
| committer | JosJuice <josjuice@gmail.com> | 2018-05-20 13:44:38 +0200 |
| commit | 17e65a7167caaf9d55ae7ea0141c298d02689ea2 (patch) | |
| tree | a85016919258539bddc6d3847fe8932c6d882361 /Source/Core | |
| parent | 0706add5840edcc7faeca538dafcd4f56cee7940 (diff) | |
VideoCommon: Replace SOIL with libpng for hires textures
Diffstat (limited to 'Source/Core')
| -rw-r--r-- | Source/Core/Common/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | Source/Core/Common/Common.vcxproj | 5 | ||||
| -rw-r--r-- | Source/Core/Common/Common.vcxproj.filters | 2 | ||||
| -rw-r--r-- | Source/Core/Common/Image.cpp | 180 | ||||
| -rw-r--r-- | Source/Core/Common/Image.h | 15 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/HiresTextures.cpp | 31 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/HiresTextures.h | 7 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp | 34 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/TextureCacheBase.cpp | 6 |
9 files changed, 227 insertions, 55 deletions
diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index cf87fd6e38..c0e3e94462 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(common GekkoDisassembler.cpp Hash.cpp HttpRequest.cpp + Image.cpp IniFile.cpp JitRegister.cpp Logging/LogManager.cpp @@ -54,6 +55,7 @@ PUBLIC PRIVATE ${CURL_LIBRARIES} ${ICONV_LIBRARIES} + png ${VTUNE_LIBRARIES} ) diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index db4031ed0a..c03dd0b6ac 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -125,6 +125,7 @@ <ClInclude Include="FloatUtils.h" /> <ClInclude Include="Hash.h" /> <ClInclude Include="HttpRequest.h" /> + <ClInclude Include="Image.h" /> <ClInclude Include="IniFile.h" /> <ClInclude Include="JitRegister.h" /> <ClInclude Include="Lazy.h" /> @@ -188,6 +189,7 @@ <ClCompile Include="GL\GLUtil.cpp" /> <ClCompile Include="Hash.cpp" /> <ClCompile Include="HttpRequest.cpp" /> + <ClCompile Include="Image.cpp" /> <ClCompile Include="IniFile.cpp" /> <ClCompile Include="JitRegister.cpp" /> <ClCompile Include="LdrWatcher.cpp" /> @@ -230,6 +232,9 @@ <ProjectReference Include="$(ExternalsDir)mbedtls\mbedTLS.vcxproj"> <Project>{bdb6578b-0691-4e80-a46c-df21639fd3b8}</Project> </ProjectReference> + <ProjectReference Include="$(ExternalsDir)libpng\png\png.vcxproj"> + <Project>{4c9f135b-a85e-430c-bad4-4c67ef5fc12c}</Project> + </ProjectReference> <ProjectReference Include="..\..\..\Externals\curl\curl.vcxproj"> <Project>{bb00605c-125f-4a21-b33b-7bf418322dcb}</Project> </ProjectReference> diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 8e0817faf1..b26498b90b 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -51,6 +51,7 @@ <ClInclude Include="FPURoundMode.h" /> <ClInclude Include="Hash.h" /> <ClInclude Include="HttpRequest.h" /> + <ClInclude Include="Image.h" /> <ClInclude Include="IniFile.h" /> <ClInclude Include="LinearDiskCache.h" /> <ClInclude Include="MathUtil.h" /> @@ -281,6 +282,7 @@ <ClCompile Include="FloatUtils.cpp" /> <ClCompile Include="Hash.cpp" /> <ClCompile Include="HttpRequest.cpp" /> + <ClCompile Include="Image.cpp" /> <ClCompile Include="IniFile.cpp" /> <ClCompile Include="MathUtil.cpp" /> <ClCompile Include="MemArena.cpp" /> diff --git a/Source/Core/Common/Image.cpp b/Source/Core/Common/Image.cpp new file mode 100644 index 0000000000..2ce3fbfabf --- /dev/null +++ b/Source/Core/Common/Image.cpp @@ -0,0 +1,180 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/Image.h" + +#include <csetjmp> +#include <cstdlib> +#include <cstring> +#include <memory> +#include <png.h> +#include <vector> + +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" + +namespace Common +{ +static int MultiplyAlpha(int alpha, int color) +{ + const int temp = (alpha * color) + 0x80; + return ((temp + (temp >> 8)) >> 8); +} + +static void PremultiplyData(png_structp png, png_row_infop row_info, png_bytep data) +{ + for (png_size_t i = 0; i < row_info->rowbytes; i += 4, data += 4) + { + const png_byte alpha = data[3]; + u32 w; + + if (alpha == 0) + { + w = 0; + } + else + { + png_byte red = data[0]; + png_byte green = data[1]; + png_byte blue = data[2]; + + if (alpha != 0xff) + { + red = MultiplyAlpha(alpha, red); + green = MultiplyAlpha(alpha, green); + blue = MultiplyAlpha(alpha, blue); + } + w = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0); + } + + std::memcpy(data, &w, sizeof(w)); + } +} + +struct ReadProgress +{ + const u8* current; + const u8* end; +}; + +static void ReadCallback(png_structp png, png_bytep data, png_size_t size) +{ + ReadProgress* progress = static_cast<ReadProgress*>(png_get_io_ptr(png)); + for (size_t i = 0; i < size; ++i) + { + if (progress->current >= progress->end) + png_error(png, "Read beyond end of file"); // This makes us longjmp back to LoadPNG + + data[i] = *(progress->current); + ++(progress->current); + } +} + +static void PNGErrorCallback(png_structp png, png_const_charp error_msg) +{ + ERROR_LOG(COMMON, "PNG loading error: %s", error_msg); + longjmp(png_jmpbuf(png), 1); +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4611) +// VS shows the following warning even though no C++ objects are destroyed in LoadPNG: +// "warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable" +#endif + +bool LoadPNG(const std::vector<u8>& input, std::vector<u8>* data_out, u32* width_out, + u32* height_out) +{ + const bool is_png = !png_sig_cmp(input.data(), 0, input.size()); + if (!is_png) + return false; + + png_struct* png = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, PNGErrorCallback, nullptr); + if (!png) + return false; + + png_info* info = png_create_info_struct(png); + if (!info) + { + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + + // It would've been nice to use std::vector for this, but using setjmp safely + // means that we have to use this manually managed volatile pointer garbage instead. + png_byte** volatile row_pointers_volatile = nullptr; + + if (setjmp(png_jmpbuf(png))) + { + free(row_pointers_volatile); + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + + ReadProgress read_progress{input.data(), input.data() + input.size()}; + png_set_read_fn(png, &read_progress, ReadCallback); + png_read_info(png, info); + + png_uint_32 width, height; + int depth, color_type, interlace; + png_get_IHDR(png, info, &width, &height, &depth, &color_type, &interlace, nullptr, nullptr); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png); + else if (color_type == PNG_COLOR_TYPE_GRAY) + png_set_expand_gray_1_2_4_to_8(png); + + if (png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png); + + if (depth == 16) + png_set_strip_16(png); + else if (depth < 8) + png_set_packing(png); + + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + if (interlace != PNG_INTERLACE_NONE) + png_set_interlace_handling(png); + + png_set_bgr(png); + png_set_filler(png, 0xff, PNG_FILLER_AFTER); + png_set_read_user_transform_fn(png, PremultiplyData); + png_read_update_info(png, info); + png_get_IHDR(png, info, &width, &height, &depth, &color_type, &interlace, nullptr, nullptr); + + const size_t stride = width * 4; + data_out->resize(stride * height); + + png_byte** row_pointers = static_cast<png_byte**>(malloc(height * sizeof(png_byte*))); + if (!row_pointers) + { + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + + for (png_uint_32 i = 0; i < height; i++) + row_pointers[i] = &(*data_out)[i * stride]; + + row_pointers_volatile = row_pointers; + + png_read_image(png, row_pointers); + png_read_end(png, info); + + free(row_pointers); + png_destroy_read_struct(&png, &info, nullptr); + + *width_out = width; + *height_out = height; + return true; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace Common diff --git a/Source/Core/Common/Image.h b/Source/Core/Common/Image.h new file mode 100644 index 0000000000..d1354b8e0b --- /dev/null +++ b/Source/Core/Common/Image.h @@ -0,0 +1,15 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <vector> + +#include "Common/CommonTypes.h" + +namespace Common +{ +bool LoadPNG(const std::vector<u8>& input, std::vector<u8>* data_out, u32* width_out, + u32* height_out); +} diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index 6a45f812fa..c6d8668a53 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -4,7 +4,6 @@ #include "VideoCommon/HiresTextures.h" -#include <SOIL/SOIL.h> #include <algorithm> #include <cinttypes> #include <cstring> @@ -22,6 +21,7 @@ #include "Common/FileUtil.h" #include "Common/Flag.h" #include "Common/Hash.h" +#include "Common/Image.h" #include "Common/Logging/Log.h" #include "Common/MemoryUtil.h" #include "Common/StringUtil.h" @@ -49,10 +49,6 @@ static std::thread s_prefetcher; static const std::string s_format_prefix = "tex1_"; -HiresTexture::Level::Level() : data(nullptr, SOIL_free_image_data) -{ -} - void HiresTexture::Init() { Update(); @@ -92,10 +88,7 @@ void HiresTexture::Update() const std::string& game_id = SConfig::GetInstance().GetGameID(); const std::string texture_directory = GetTextureDirectory(game_id); - std::vector<std::string> extensions{ - ".png", ".bmp", ".tga", ".dds", - ".jpg" // Why not? Could be useful for large photo-like textures - }; + const std::vector<std::string> extensions{".png", ".dds"}; const std::vector<std::string> texture_paths = Common::DoFileSearch({texture_directory}, extensions, /*recursive*/ true); @@ -183,9 +176,7 @@ void HiresTexture::Prefetch() if (iter != s_textureCache.end()) { for (const Level& l : iter->second->m_levels) - { - size_sum += l.data_size; - } + size_sum += l.data.size(); } } @@ -361,6 +352,7 @@ std::unique_ptr<HiresTexture> HiresTexture::Load(const std::string& base_filenam file.Open(filename_iter->second.path, "rb"); std::vector<u8> buffer(file.GetSize()); file.ReadBytes(buffer.data(), file.GetSize()); + if (!LoadTexture(level, buffer)) { ERROR_LOG(VIDEO, "Custom texture %s failed to load", filename.c_str()); @@ -440,22 +432,15 @@ std::unique_ptr<HiresTexture> HiresTexture::Load(const std::string& base_filenam bool HiresTexture::LoadTexture(Level& level, const std::vector<u8>& buffer) { - int channels; - int width; - int height; + if (!Common::LoadPNG(buffer, &level.data, &level.width, &level.height)) + return false; - u8* data = SOIL_load_image_from_memory(buffer.data(), static_cast<int>(buffer.size()), &width, - &height, &channels, SOIL_LOAD_RGBA); - if (!data) + if (level.data.empty()) return false; - // Images loaded by SOIL are converted to RGBA. - level.width = static_cast<u32>(width); - level.height = static_cast<u32>(height); + // Loaded PNG images are converted to RGBA. level.format = AbstractTextureFormat::RGBA8; - level.data = ImageDataPointer(data, SOIL_free_image_data); level.row_length = level.width; - level.data_size = static_cast<size_t>(level.row_length) * 4 * level.height; return true; } diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h index 539466a9a8..af79a02d53 100644 --- a/Source/Core/VideoCommon/HiresTextures.h +++ b/Source/Core/VideoCommon/HiresTextures.h @@ -16,8 +16,6 @@ enum class TextureFormat; class HiresTexture { public: - using ImageDataPointer = std::unique_ptr<u8, void (*)(unsigned char*)>; - static void Init(); static void Update(); static void Shutdown(); @@ -39,14 +37,11 @@ public: struct Level { - Level(); - - ImageDataPointer data; + std::vector<u8> data; AbstractTextureFormat format = AbstractTextureFormat::RGBA8; u32 width = 0; u32 height = 0; u32 row_length = 0; - size_t data_size = 0; }; std::vector<Level> m_levels; diff --git a/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp b/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp index 003a59664f..5a3d7788ca 100644 --- a/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp +++ b/Source/Core/VideoCommon/HiresTextures_DDSLoader.cpp @@ -155,14 +155,9 @@ u32 GetBlockCount(u32 extent, u32 block_size) return std::max(Common::AlignUp(extent, block_size) / block_size, 1u); } -HiresTexture::ImageDataPointer AllocateLevelData(size_t size) -{ - return HiresTexture::ImageDataPointer(new u8[size], [](u8* data) { delete[] data; }); -} - void ConvertTexture_X8B8G8R8(HiresTexture::Level* level) { - u8* data_ptr = level->data.get(); + u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) { for (u32 x = 0; x < level->row_length; x++) @@ -176,7 +171,7 @@ void ConvertTexture_X8B8G8R8(HiresTexture::Level* level) void ConvertTexture_A8R8G8B8(HiresTexture::Level* level) { - u8* data_ptr = level->data.get(); + u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) { for (u32 x = 0; x < level->row_length; x++) @@ -193,7 +188,7 @@ void ConvertTexture_A8R8G8B8(HiresTexture::Level* level) void ConvertTexture_X8R8G8B8(HiresTexture::Level* level) { - u8* data_ptr = level->data.get(); + u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) { for (u32 x = 0; x < level->row_length; x++) @@ -210,14 +205,10 @@ void ConvertTexture_X8R8G8B8(HiresTexture::Level* level) void ConvertTexture_R8G8B8(HiresTexture::Level* level) { - // Have to reallocate the buffer for this one, since the data in the file - // does not have an alpha byte. - level->data_size = level->row_length * level->height * sizeof(u32); - HiresTexture::ImageDataPointer rgb_data = AllocateLevelData(level->data_size); - std::swap(level->data, rgb_data); + std::vector<u8> new_data(level->row_length * level->height * sizeof(u32)); - const u8* rgb_data_ptr = rgb_data.get(); - u8* data_ptr = level->data.get(); + const u8* rgb_data_ptr = level->data.data(); + u8* data_ptr = new_data.data(); for (u32 row = 0; row < level->height; row++) { @@ -232,6 +223,8 @@ void ConvertTexture_R8G8B8(HiresTexture::Level* level) rgb_data_ptr += 3; } } + + level->data = std::move(new_data); } bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) @@ -410,19 +403,14 @@ bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) bool ReadMipLevel(HiresTexture::Level* level, File::IOFile& file, const DDSLoadInfo& info, u32 width, u32 height, u32 row_length, size_t size) { - // Copy to the final storage location. The deallocator here is simple, nothing extra is - // needed, compared to the SOIL-based loader. + // Copy to the final storage location. level->width = width; level->height = height; level->format = info.format; level->row_length = row_length; - level->data_size = size; - level->data = AllocateLevelData(level->data_size); - if (!file.ReadBytes(level->data.get(), level->data_size)) - { - level->data.reset(); + level->data.resize(size); + if (!file.ReadBytes(level->data.data(), level->data.size())) return false; - } // Apply conversion function for uncompressed textures. if (info.conversion_function) diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index e68b408cff..48518911bd 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -957,8 +957,8 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo if (hires_tex) { const auto& level = hires_tex->m_levels[0]; - entry->texture->Load(0, level.width, level.height, level.row_length, level.data.get(), - level.data_size); + entry->texture->Load(0, level.width, level.height, level.row_length, level.data.data(), + level.data.size()); } // Initialized to null because only software loading uses this buffer @@ -1042,7 +1042,7 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo { const auto& level = hires_tex->m_levels[level_index]; entry->texture->Load(level_index, level.width, level.height, level.row_length, - level.data.get(), level.data_size); + level.data.data(), level.data.size()); } } else |
