// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // 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 LOG_CHANNEL(DynamicLibrary); // Ensure it's in .bss static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); // 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 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); }