You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
duckstation/src/util/dyn_libs.cpp

334 lines
11 KiB
C++

// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "dyn_freetype.h"
#include "dyn_libjpeg.h"
#include "dyn_libpng.h"
#include "dyn_libwebp.h"
#include "dyn_libzip.h"
#include "dyn_plutosvg.h"
#include "dyn_sdl.h"
#include "dyn_shaderc.h"
#include "dyn_spirv_cross.h"
#include "dyn_sqlite.h"
#include "common/assert.h"
#include "common/dynamic_library.h"
#include "common/error.h"
#include "common/log.h"
#include <utility>
LOG_CHANNEL(DynamicLibrary);
// Ensure it's in .bss
static_assert(std::is_trivially_copyable_v<DynFreeType> && std::is_standard_layout_v<DynFreeType>);
static_assert(std::is_trivially_copyable_v<DynPlutoSvg> && std::is_standard_layout_v<DynPlutoSvg>);
static_assert(std::is_trivially_copyable_v<DynLibPNG> && std::is_standard_layout_v<DynLibPNG>);
static_assert(std::is_trivially_copyable_v<DynLibJPEG> && std::is_standard_layout_v<DynLibJPEG>);
static_assert(std::is_trivially_copyable_v<DynLibWebP> && std::is_standard_layout_v<DynLibWebP>);
static_assert(std::is_trivially_copyable_v<DynLibZip> && std::is_standard_layout_v<DynLibZip>);
static_assert(std::is_trivially_copyable_v<DynSDL> && std::is_standard_layout_v<DynSDL>);
static_assert(std::is_trivially_copyable_v<DynShaderc> && std::is_standard_layout_v<DynShaderc>);
static_assert(std::is_trivially_copyable_v<DynSpirvCross> && std::is_standard_layout_v<DynSpirvCross>);
static_assert(std::is_trivially_copyable_v<DynSqlite> && std::is_standard_layout_v<DynSqlite>);
// Because of course friggin linux is different...
#ifdef _WIN32
static constexpr int FREETYPE_MAJOR_VERSION = -1;
static constexpr int PLUTOSVG_MAJOR_VERSION = -1;
static constexpr int LIBPNG_MAJOR_VERSION = -1;
static constexpr int LIBJPEG_MAJOR_VERSION = -1;
static constexpr int LIBWEBP_MAJOR_VERSION = -1;
static constexpr int LIBSDL_MAJOR_VERSION = -1;
static constexpr int LIBZIP_MAJOR_VERSION = -1;
static constexpr int SQLITE_MAJOR_VERSION = -1;
static constexpr int SPIRV_CROSS_MAJOR_VERSION = -1;
#else
static constexpr int FREETYPE_MAJOR_VERSION = 6;
static constexpr int PLUTOSVG_MAJOR_VERSION = 0;
static constexpr int LIBPNG_MAJOR_VERSION = 16;
static constexpr int LIBJPEG_MAJOR_VERSION = 62;
static constexpr int LIBWEBP_MAJOR_VERSION = 7;
static constexpr int LIBZIP_MAJOR_VERSION = 5;
static constexpr int LIBSDL_MAJOR_VERSION = 0;
static constexpr int SQLITE_MAJOR_VERSION = 3;
static constexpr int SPIRV_CROSS_MAJOR_VERSION = SPVC_C_API_VERSION_MAJOR;
#endif
namespace {
struct Locals
{
DynamicLibrary freetype_library;
std::once_flag freetype_init_flag;
DynamicLibrary plutosvg_library;
std::once_flag plutosvg_init_flag;
DynamicLibrary libjpeg_library;
std::once_flag libjpeg_init_flag;
DynamicLibrary libpng_library;
std::once_flag libpng_init_flag;
DynamicLibrary libwebp_library;
std::once_flag libwebp_init_flag;
DynamicLibrary libzip_library;
std::once_flag libzip_init_flag;
DynamicLibrary sdl_library;
std::once_flag sdl_init_flag;
DynamicLibrary sqlite_library;
std::once_flag sqlite_init_flag;
DynamicLibrary shaderc_library;
std::once_flag shaderc_init_flag;
shaderc_compiler_t shaderc_compiler;
DynamicLibrary spirv_cross_library;
std::once_flag spirv_cross_init_flag;
#if defined(_DEBUG) || defined(_DEVEL)
~Locals();
#endif
};
} // namespace
DynFreeType g_dyn_freetype;
DynPlutoSvg g_dyn_plutosvg;
DynLibJPEG g_dyn_libjpeg;
DynLibWebP g_dyn_libwebp;
DynLibPNG g_dyn_libpng;
DynLibZip g_dyn_libzip;
DynSDL g_dyn_sdl;
DynSqlite g_dyn_sqlite;
DynShaderc g_dyn_shaderc;
DynSpirvCross g_dyn_spirv_cross;
static Locals s_locals;
#if defined(_DEBUG) || defined(_DEVEL)
Locals::~Locals()
{
DebugAssert(!s_locals.shaderc_compiler);
}
#endif
static bool LoadDynLib(const char* libname, int major_version, DynamicLibrary& dynlib, std::once_flag& once_flag,
std::span<const DynamicLibrary::SymbolTable> symbols, Error* const error,
bool (*extra_loader)(Error* const) = nullptr)
{
std::call_once(once_flag, [&libname, &major_version, &dynlib, &symbols, extra_loader, error]() {
Error lerror;
DynamicLibrary lib;
if (!lib.Open(DynamicLibrary::GetVersionedFilename(libname, major_version).c_str(), &lerror) ||
!lib.ResolveSymbols(symbols.data(), symbols.size(), &lerror))
{
ERROR_LOG("Failed to load {}: {}", libname, lerror.GetDescription());
Error::SetStringFmt(error, "Failed to load {}: {}", libname, lerror.GetDescription());
return;
}
if (extra_loader && !extra_loader(error))
{
DynamicLibrary::ClearSymbols(symbols.data(), symbols.size());
return;
}
dynlib = std::move(lib);
});
return dynlib.IsOpen();
}
static const DynamicLibrary::SymbolTable s_freetype_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_freetype.F},
DYN_FREETYPE_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynFreeType::Open(Error* const error)
{
if (s_locals.freetype_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("freetype", FREETYPE_MAJOR_VERSION, s_locals.freetype_library, s_locals.freetype_init_flag,
s_freetype_symbols, error);
}
static const DynamicLibrary::SymbolTable s_plutosvg_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_plutosvg.F},
DYN_PLUTOSVG_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynPlutoSvg::Open(Error* const error)
{
if (s_locals.plutosvg_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("plutosvg", PLUTOSVG_MAJOR_VERSION, s_locals.plutosvg_library, s_locals.plutosvg_init_flag,
s_plutosvg_symbols, error);
}
static const DynamicLibrary::SymbolTable s_libpng_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_libpng.F},
DYN_LIBPNG_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynLibPNG::Open(Error* const error)
{
if (s_locals.libpng_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("libpng16", LIBPNG_MAJOR_VERSION, s_locals.libpng_library, s_locals.libpng_init_flag,
s_libpng_symbols, error);
}
static const DynamicLibrary::SymbolTable s_libjpeg_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_libjpeg.F},
DYN_LIBJPEG_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynLibJPEG::Open(Error* const error)
{
if (s_locals.libjpeg_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("jpeg", LIBJPEG_MAJOR_VERSION, s_locals.libjpeg_library, s_locals.libjpeg_init_flag,
s_libjpeg_symbols, error);
}
static const DynamicLibrary::SymbolTable s_libwebp_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_libwebp.F},
DYN_LIBWEBP_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynLibWebP::Open(Error* const error)
{
if (s_locals.libwebp_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("webp", LIBWEBP_MAJOR_VERSION, s_locals.libwebp_library, s_locals.libwebp_init_flag,
s_libwebp_symbols, error);
}
static const DynamicLibrary::SymbolTable s_libzip_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_libzip.F},
DYN_LIBZIP_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynLibZip::Open(Error* const error)
{
if (s_locals.libzip_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("zip", LIBZIP_MAJOR_VERSION, s_locals.libzip_library, s_locals.libzip_init_flag, s_libzip_symbols,
error);
}
static const DynamicLibrary::SymbolTable s_sdl_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_sdl.F},
DYN_SDL_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
static const DynamicLibrary::SymbolTable s_sqlite_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_sqlite.F},
DYN_SQLITE_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynSqlite::Open(Error* error)
{
if (s_locals.sqlite_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("sqlite3", SQLITE_MAJOR_VERSION, s_locals.sqlite_library, s_locals.sqlite_init_flag,
s_sqlite_symbols, error);
}
static void SDLLogCallback(void* userdata, int category, SDL_LogPriority priority, const char* message)
{
static constexpr Log::Level priority_map[SDL_LOG_PRIORITY_COUNT] = {
Log::Level::Debug, // SDL_LOG_PRIORITY_INVALID
Log::Level::Trace, // SDL_LOG_PRIORITY_TRACE
Log::Level::Verbose, // SDL_LOG_PRIORITY_VERBOSE
Log::Level::Debug, // SDL_LOG_PRIORITY_DEBUG
Log::Level::Info, // SDL_LOG_PRIORITY_INFO
Log::Level::Warning, // SDL_LOG_PRIORITY_WARN
Log::Level::Error, // SDL_LOG_PRIORITY_ERROR
Log::Level::Error, // SDL_LOG_PRIORITY_CRITICAL
};
GENERIC_LOG(Log::Channel::SDL, priority_map[priority], Log::Color::Default, message);
}
static bool SDLLoadCallback(Error* const error)
{
g_dyn_sdl.SDL_SetLogOutputFunction(SDLLogCallback, nullptr);
#if defined(_DEBUG) || defined(_DEVEL)
g_dyn_sdl.SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG);
#else
g_dyn_sdl.SDL_SetLogPriorities(SDL_LOG_PRIORITY_INFO);
#endif
return true;
}
bool DynSDL::Open(Error* error)
{
if (s_locals.sdl_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("SDL3", LIBSDL_MAJOR_VERSION, s_locals.sdl_library, s_locals.sdl_init_flag, s_sdl_symbols, error,
SDLLoadCallback);
}
static const DynamicLibrary::SymbolTable s_shaderc_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_shaderc.F},
DYN_SHADERC_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynShaderc::Open(Error* const error)
{
if (s_locals.shaderc_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("shaderc_shared", -1, s_locals.shaderc_library, s_locals.shaderc_init_flag, s_shaderc_symbols,
error, [](Error* const error) {
g_dyn_shaderc.compiler = g_dyn_shaderc.shaderc_compiler_initialize();
if (!g_dyn_shaderc.compiler)
{
ERROR_LOG("shaderc_compiler_initialize() failed");
Error::SetStringView(error, "shaderc_compiler_initialize() failed");
return false;
}
return true;
});
}
void DynShaderc::Close()
{
if (compiler)
shaderc_compiler_release(std::exchange(compiler, nullptr));
}
// clang-format off
static const DynamicLibrary::SymbolTable s_spirv_cross_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_spirv_cross.F},
DYN_SPIRV_CROSS_FUNCTIONS(RESOLVE_SYMBOL)
DYN_SPIRV_CROSS_HLSL_FUNCTIONS(RESOLVE_SYMBOL)
DYN_SPIRV_CROSS_MSL_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
// clang-format on
bool DynSpirvCross::Open(Error* const error)
{
if (s_locals.spirv_cross_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("spirv-cross-c-shared", SPIRV_CROSS_MAJOR_VERSION, s_locals.spirv_cross_library,
s_locals.spirv_cross_init_flag, s_spirv_cross_symbols, error);
}