common: Move image decoders to common

* Replace the various image interfaces with spng which is very lightweight and fast. Also add a dds header which will be useful when support for that format is implemented
This commit is contained in:
GPUCode
2023-02-15 23:46:52 +02:00
parent 65d9ba7db5
commit 87b23fdb10
24 changed files with 322 additions and 129 deletions

6
.gitmodules vendored
View File

@ -43,9 +43,6 @@
[submodule "teakra"]
path = externals/teakra
url = https://github.com/wwylele/teakra.git
[submodule "lodepng"]
path = externals/lodepng/lodepng
url = https://github.com/lvandeve/lodepng.git
[submodule "zstd"]
path = externals/zstd
url = https://github.com/facebook/zstd.git
@ -73,3 +70,6 @@
[submodule "sirit"]
path = externals/sirit
url = https://github.com/GPUCode/sirit
[submodule "externals/libspng"]
path = externals/libspng
url = https://github.com/randy408/libspng

View File

@ -172,8 +172,8 @@ if (ENABLE_WEB_SERVICE)
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
endif()
# lodepng
add_subdirectory(lodepng)
# libspng
add_subdirectory(libspng)
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
if(ANDROID)

1
externals/libspng vendored Submodule

Submodule externals/libspng added at 75c39ce094

View File

@ -1,7 +0,0 @@
add_library(lodepng
lodepng/lodepng.cpp
lodepng/lodepng.h
)
create_target_directory_groups(lodepng)
target_include_directories(lodepng INTERFACE lodepng)

View File

@ -8,8 +8,6 @@ add_executable(citra
default_ini.h
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
lodepng_image_interface.cpp
lodepng_image_interface.h
precompiled_headers.h
resource.h
)
@ -17,7 +15,7 @@ add_executable(citra
create_target_directory_groups(citra)
target_link_libraries(citra PRIVATE common core input_common network)
target_link_libraries(citra PRIVATE inih glad lodepng)
target_link_libraries(citra PRIVATE inih glad)
if (MSVC)
target_link_libraries(citra PRIVATE getopt)
endif()

View File

@ -10,7 +10,6 @@
// This needs to be included before getopt.h because the latter #defines symbols used by it
#include "citra/config.h"
#include "citra/emu_window/emu_window_sdl2.h"
#include "citra/lodepng_image_interface.h"
#include "common/common_paths.h"
#include "common/detached_tasks.h"
#include "common/file_util.h"
@ -351,9 +350,6 @@ int main(int argc, char** argv) {
// Register frontend applets
Frontend::RegisterDefaultApplets();
// Register generic image interface
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
EmuWindow_SDL2::InitializeSDL2();
const auto emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen, false)};

View File

@ -1,29 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <lodepng.h>
#include "citra/lodepng_image_interface.h"
#include "common/logging/log.h"
bool LodePNGImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
const std::string& path) {
u32 lodepng_ret = lodepng::decode(dst, width, height, path);
if (lodepng_ret) {
LOG_CRITICAL(Frontend, "Failed to decode {} because {}", path,
lodepng_error_text(lodepng_ret));
return false;
}
return true;
}
bool LodePNGImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src,
u32 width, u32 height) {
u32 lodepng_ret = lodepng::encode(path, src, width, height);
if (lodepng_ret) {
LOG_CRITICAL(Frontend, "Failed to encode {} because {}", path,
lodepng_error_text(lodepng_ret));
return false;
}
return true;
}

View File

@ -1,14 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/frontend/image_interface.h"
class LodePNGImageInterface final : public Frontend::ImageInterface {
public:
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
u32 height) override;
};

View File

@ -167,8 +167,6 @@ add_executable(citra-qt
precompiled_headers.h
uisettings.cpp
uisettings.h
qt_image_interface.cpp
qt_image_interface.h
updater/updater.cpp
updater/updater.h
updater/updater_p.h

View File

@ -50,7 +50,6 @@
#include "citra_qt/movie/movie_play_dialog.h"
#include "citra_qt/movie/movie_record_dialog.h"
#include "citra_qt/multiplayer/state.h"
#include "citra_qt/qt_image_interface.h"
#include "citra_qt/uisettings.h"
#include "citra_qt/updater/updater.h"
#include "citra_qt/util/clickable_label.h"
@ -2649,9 +2648,6 @@ int main(int argc, char* argv[]) {
system.RegisterMiiSelector(std::make_shared<QtMiiSelector>(main_window));
system.RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window));
// Register Qt image interface
system.RegisterImageInterface(std::make_shared<QtImageInterface>());
main_window.show();
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,

View File

@ -1,38 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QImage>
#include <QString>
#include "citra_qt/qt_image_interface.h"
#include "common/logging/log.h"
bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
const std::string& path) {
QImage image(QString::fromStdString(path));
if (image.isNull()) {
LOG_ERROR(Frontend, "Failed to open {} for decoding", path);
return false;
}
width = image.width();
height = image.height();
image = image.convertToFormat(QImage::Format_RGBA8888);
// Write RGBA8 to vector
dst = std::vector<u8>(image.constBits(), image.constBits() + (width * height * 4));
return true;
}
bool QtImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
u32 height) {
QImage image(src.data(), width, height, QImage::Format_RGBA8888);
if (!image.save(QString::fromStdString(path), "PNG")) {
LOG_ERROR(Frontend, "Failed to save {}", path);
return false;
}
return true;
}

View File

@ -1,14 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/frontend/image_interface.h"
class QtImageInterface final : public Frontend::ImageInterface {
public:
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
u32 height) override;
};

View File

@ -74,11 +74,14 @@ add_library(common STATIC
common_precompiled_headers.h
common_types.h
construct.h
dds.h
error.cpp
error.h
file_util.cpp
file_util.h
hash.h
image_util.cpp
image_util.h
linear_disk_cache.h
literals.h
logging/backend.cpp
@ -143,7 +146,7 @@ add_library(common STATIC
create_target_directory_groups(common)
target_link_libraries(common PUBLIC fmt::fmt microprofile Boost::boost Boost::serialization)
target_link_libraries(common PRIVATE libzstd_static)
target_link_libraries(common PRIVATE libzstd_static spng_static)
set_target_properties(common PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
if ("x86_64" IN_LIST ARCHITECTURE)

111
src/common/dds.h Normal file
View File

@ -0,0 +1,111 @@
//--------------------------------------------------------------------------------------
// DDS.h
//
// This header defines constants and structures that are useful when parsing
// DDS files. DDS files were originally designed to use several structures
// and constants that are native to DirectDraw and are defined in ddraw.h,
// such as DDSURFACEDESC2 and DDSCAPS2. This file defines similar
// (compatible) constants and structures so that one can use DDS files
// without needing to include ddraw.h.
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
// http://go.microsoft.com/fwlink/?LinkId=248929
// http://go.microsoft.com/fwlink/?LinkID=615561
//--------------------------------------------------------------------------------------
#pragma once
#include <cstdint>
namespace Common::DirectX {
#pragma pack(push, 1)
const uint32_t DDS_MAGIC = 0x20534444; // "DDS "
struct DDS_PIXELFORMAT {
uint32_t dwSize;
uint32_t dwFlags;
uint32_t dwFourCC;
uint32_t dwRGBBitCount;
uint32_t dwRBitMask;
uint32_t dwGBitMask;
uint32_t dwBBitMask;
uint32_t dwABitMask;
};
#define DDS_FOURCC 0x00000004 // DDPF_FOURCC
#define DDS_RGB 0x00000040 // DDPF_RGB
#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS
#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE
#define DDS_LUMINANCEA 0x00020001 // DDPF_LUMINANCE | DDPF_ALPHAPIXELS
#define DDS_ALPHA 0x00000002 // DDPF_ALPHA
#define DDS_PAL8 0x00000020 // DDPF_PALETTEINDEXED8
#define DDS_PAL8A 0x00000021 // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS
#define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV
#ifndef MAKEFOURCC
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) | \
((uint32_t)(uint8_t)(ch2) << 16) | ((uint32_t)(uint8_t)(ch3) << 24))
#endif /* defined(MAKEFOURCC) */
#define DDS_HEADER_FLAGS_TEXTURE \
0x00001007 // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
#define DDS_HEADER_FLAGS_MIPMAP 0x00020000 // DDSD_MIPMAPCOUNT
#define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH
#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH
#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE
// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION
enum DDS_RESOURCE_DIMENSION {
DDS_DIMENSION_TEXTURE1D = 2,
DDS_DIMENSION_TEXTURE2D = 3,
DDS_DIMENSION_TEXTURE3D = 4,
};
struct DDS_HEADER {
uint32_t dwSize;
uint32_t dwFlags;
uint32_t dwHeight;
uint32_t dwWidth;
uint32_t dwPitchOrLinearSize;
uint32_t dwDepth; // only if DDS_HEADER_FLAGS_VOLUME is set in dwFlags
uint32_t dwMipMapCount;
uint32_t dwReserved1[11];
DDS_PIXELFORMAT ddspf;
uint32_t dwCaps;
uint32_t dwCaps2;
uint32_t dwCaps3;
uint32_t dwCaps4;
uint32_t dwReserved2;
};
struct DDS_HEADER_DXT10 {
uint32_t dxgiFormat;
uint32_t resourceDimension;
uint32_t miscFlag; // see DDS_RESOURCE_MISC_FLAG
uint32_t arraySize;
uint32_t miscFlags2; // see DDS_MISC_FLAGS2
};
#pragma pack(pop)
static_assert(sizeof(DDS_HEADER) == 124, "DDS Header size mismatch");
static_assert(sizeof(DDS_HEADER_DXT10) == 20, "DDS DX10 Extended Header size mismatch");
constexpr DDS_PIXELFORMAT DDSPF_A8R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000};
constexpr DDS_PIXELFORMAT DDSPF_X8R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000};
constexpr DDS_PIXELFORMAT DDSPF_A8B8G8R8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000};
constexpr DDS_PIXELFORMAT DDSPF_X8B8G8R8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000};
constexpr DDS_PIXELFORMAT DDSPF_R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000};
} // namespace Common::DirectX

View File

@ -339,6 +339,9 @@ public:
[[nodiscard]] explicit operator bool() const {
return IsGood();
}
[[nodiscard]] std::FILE* Handle() {
return m_file;
}
bool Seek(s64 off, int origin);
[[nodiscard]] u64 Tell() const;

111
src/common/image_util.cpp Normal file
View File

@ -0,0 +1,111 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <spng.h>
#include "common/dds.h"
#include "common/file_util.h"
#include "common/image_util.h"
#include "common/logging/log.h"
namespace Common {
using namespace Common::DirectX;
namespace {
void spng_free(spng_ctx* ctx) {
if (ctx) {
spng_ctx_free(ctx);
}
}
auto make_spng_ctx(int flags) {
return std::unique_ptr<spng_ctx, decltype(&spng_free)>(spng_ctx_new(flags), spng_free);
}
} // Anonymous namespace
bool DecodePNG(std::span<const u8> in_data, std::vector<u8>& out_data, u32& width, u32& height) {
auto ctx = make_spng_ctx(0);
if (!ctx) [[unlikely]] {
return false;
}
if (spng_set_png_buffer(ctx.get(), in_data.data(), in_data.size())) {
return false;
}
spng_ihdr ihdr{};
if (spng_get_ihdr(ctx.get(), &ihdr)) {
return false;
}
const int format = SPNG_FMT_RGBA8;
size_t decoded_len = 0;
if (spng_decoded_image_size(ctx.get(), format, &decoded_len)) {
return false;
}
out_data.resize(decoded_len);
if (spng_decode_image(ctx.get(), out_data.data(), decoded_len, format, SPNG_DECODE_TRNS)) {
return false;
}
width = ihdr.width;
height = ihdr.height;
return true;
}
bool EncodePNG(std::span<u8> in_data, const std::string& out_path, u32 width, u32 height,
u32 stride, s32 level) {
auto ctx = make_spng_ctx(SPNG_CTX_ENCODER);
if (!ctx) [[unlikely]] {
return false;
}
auto outfile = FileUtil::IOFile(out_path, "wb");
if (spng_set_png_file(ctx.get(), outfile.Handle())) {
return false;
}
if (spng_set_option(ctx.get(), SPNG_IMG_COMPRESSION_LEVEL, level)) {
return false;
}
spng_ihdr ihdr{};
ihdr.width = width;
ihdr.height = height;
ihdr.color_type = SPNG_COLOR_TYPE_TRUECOLOR_ALPHA;
ihdr.bit_depth = 8;
if (spng_set_ihdr(ctx.get(), &ihdr)) {
return false;
}
if (spng_encode_image(ctx.get(), nullptr, 0, SPNG_FMT_PNG,
SPNG_ENCODE_PROGRESSIVE | SPNG_ENCODE_FINALIZE)) {
return false;
}
for (u32 row = 0; row < height; row++) {
const int err = spng_encode_row(ctx.get(), &in_data[row * stride], stride);
if (err == SPNG_EOI) {
break;
}
if (err) {
LOG_ERROR(Common, "Failed to save {} by {} image to {} at level {}: error {}", width,
height, out_path, level, err);
return false;
}
}
size_t image_len = 0;
spng_decoded_image_size(ctx.get(), SPNG_FMT_PNG, &image_len);
LOG_ERROR(Common, "{} byte {} by {} image saved to {} at level {}", image_len, width, height,
out_path, level);
return true;
}
} // namespace Common

25
src/common/image_util.h Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <span>
#include <vector>
#include "common/common_types.h"
namespace Common {
/**
* @brief DecodePNG Given a buffer of png input data decodes said data to RGBA8 format
* and writes the result to out_data, updating width and height to match the file dimentions
* @param in_data The input png data
* @param out_data The decoded RGBA8 pixel data
* @param width The output width of the png image
* @param height The output height of the png image
* @return true on decode success, false otherwise
*/
bool DecodePNG(std::span<const u8> in_data, std::vector<u8>& out_data, u32& width, u32& height);
bool EncodePNG(std::span<u8> in_data, const std::string& out_path, u32 width, u32 height,
u32 stride, s32 level);
} // namespace Common

View File

@ -83,14 +83,14 @@ void CustomTexCache::PreloadTextures(Frontend::ImageInterface& image_interface)
std::bitset<32> width_bits(tex_info.width);
std::bitset<32> height_bits(tex_info.height);
if (width_bits.count() == 1 && height_bits.count() == 1) {
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
LOG_DEBUG(HW_GPU, "Loaded custom texture from {}", path_info.path);
Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
CacheTexture(path_info.hash, tex_info.tex, tex_info.width, tex_info.height);
} else {
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
LOG_ERROR(HW_GPU, "Texture {} size is not a power of 2", path_info.path);
}
} else {
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
LOG_ERROR(HW_GPU, "Failed to load custom texture {}", path_info.path);
}
}
}

View File

@ -12,7 +12,7 @@
namespace Frontend {
class ImageInterface;
} // namespace Frontend
}
namespace Core {
struct CustomTexInfo {

View File

@ -28,6 +28,8 @@ add_library(video_core STATIC
regs_texturing.h
renderer_base.cpp
renderer_base.h
rasterizer_cache/custom/custom_tex_manager.cpp
rasterizer_cache/custom/custom_tex_manager.h
rasterizer_cache/framebuffer_base.cpp
rasterizer_cache/framebuffer_base.h
rasterizer_cache/pixel_format.cpp

View File

@ -0,0 +1,53 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "common/common_types.h"
namespace VideoCore {
struct CustomTexture {
u32 width;
u32 height;
u32 levels;
std::vector<u8> pixels;
};
// This is to avoid parsing the filename multiple times
struct CustomTexPathInfo {
std::string path;
u64 hash;
};
// TODO: think of a better name for this class...
class CustomTexManager {
public:
explicit CustomTexManager();
~CustomTexManager();
bool IsTextureDumped(u64 hash) const;
void SetTextureDumped(u64 hash);
bool IsTextureCached(u64 hash) const;
const CustomTexture& LookupTexture(u64 hash) const;
void CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height);
void AddTexturePath(u64 hash, const std::string& path);
void FindCustomTextures(u64 program_id);
void PreloadTextures(Frontend::ImageInterface& image_interface);
bool CustomTextureExists(u64 hash) const;
const CustomTexPathInfo& LookupTexturePathInfo(u64 hash) const;
bool IsTexturePathMapEmpty() const;
private:
std::unordered_set<u64> dumped_textures;
std::unordered_map<u64, CustomTexInfo> custom_textures;
std::unordered_map<u64, CustomTexPathInfo> custom_texture_paths;
};
} // namespace VideoCore

View File

@ -173,9 +173,7 @@ Allocation TextureRuntime::Allocate(u32 width, u32 height, u32 levels,
vk::ImageUsageFlags usage, vk::ImageAspectFlags aspect) {
MICROPROFILE_SCOPE(Vulkan_ImageAlloc);
// The internal format does not provide enough guarantee of texture uniqueness
// especially when many pixel formats fallback to RGBA8
ASSERT(pixel_format != VideoCore::PixelFormat::Invalid);
ASSERT(pixel_format != VideoCore::PixelFormat::Invalid && levels >= 1);
const HostTextureTag key = {
.format = format,
.pixel_format = pixel_format,