Vulkan: Simplify loader using DynamicLibrary

This commit is contained in:
Stenzek 2024-05-15 00:18:49 +10:00
parent 88ace6e4ae
commit 8e3284d8c6
No known key found for this signature in database
9 changed files with 109 additions and 139 deletions

View file

@ -31,6 +31,9 @@ void RemoveThemeChangeHandler(void* ctx);
/// Moves a file from one location to another, using NSFileManager. /// Moves a file from one location to another, using NSFileManager.
bool MoveFile(const char* source, const char* destination, Error* error); bool MoveFile(const char* source, const char* destination, Error* error);
/// Returns the bundle path.
std::optional<std::string> GetBundlePath();
/// Get the bundle path to the actual application without any translocation fun /// Get the bundle path to the actual application without any translocation fun
std::optional<std::string> GetNonTranslocatedBundlePath(); std::optional<std::string> GetNonTranslocatedBundlePath();

View file

@ -107,6 +107,17 @@ void CocoaTools::RemoveThemeChangeHandler(void* ctx)
[s_themeChangeHandler removeCallback:ctx]; [s_themeChangeHandler removeCallback:ctx];
} }
std::optional<std::string> CocoaTools::GetBundlePath()
{
std::optional<std::string> ret;
@autoreleasepool {
NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
if (url)
ret = std::string([url fileSystemRepresentation]);
}
return ret;
}
std::optional<std::string> CocoaTools::GetNonTranslocatedBundlePath() std::optional<std::string> CocoaTools::GetNonTranslocatedBundlePath()
{ {
// See https://objective-see.com/blog/blog_0x15.html // See https://objective-see.com/blog/blog_0x15.html

View file

@ -4,7 +4,9 @@
#include "common/dynamic_library.h" #include "common/dynamic_library.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/error.h" #include "common/error.h"
#include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "common/path.h"
#include "common/small_string.h" #include "common/small_string.h"
#include "common/string_util.h" #include "common/string_util.h"
@ -15,6 +17,9 @@
#include "common/windows_headers.h" #include "common/windows_headers.h"
#else #else
#include <dlfcn.h> #include <dlfcn.h>
#ifdef __APPLE__
#include "common/cocoa_tools.h"
#endif
#endif #endif
Log_SetChannel(DynamicLibrary); Log_SetChannel(DynamicLibrary);
@ -92,6 +97,27 @@ bool DynamicLibrary::Open(const char* filename, Error* error)
m_handle = dlopen(filename, RTLD_NOW); m_handle = dlopen(filename, RTLD_NOW);
if (!m_handle) if (!m_handle)
{ {
#ifdef __APPLE__
// On MacOS, try searching in Frameworks.
if (!Path::IsAbsolute(filename))
{
std::optional<std::string> bundle_path = CocoaTools::GetBundlePath();
if (bundle_path.has_value())
{
std::string frameworks_path = fmt::format("{}/Contents/Frameworks/{}", bundle_path.value(), filename);
if (FileSystem::FileExists(frameworks_path.c_str()))
{
m_handle = dlopen(frameworks_path.c_str(), RTLD_NOW);
if (m_handle)
{
Error::Clear(error);
return true;
}
}
}
}
#endif
const char* err = dlerror(); const char* err = dlerror();
Error::SetStringFmt(error, "Loading {} failed: {}", filename, err ? err : "<UNKNOWN>"); Error::SetStringFmt(error, "Loading {} failed: {}", filename, err ? err : "<UNKNOWN>");
return false; return false;

View file

@ -28,6 +28,12 @@ void Error::Clear()
m_description = {}; m_description = {};
} }
void Error::Clear(Error* errptr)
{
if (errptr)
errptr->Clear();
}
void Error::SetErrno(int err) void Error::SetErrno(int err)
{ {
SetErrno(std::string_view(), err); SetErrno(std::string_view(), err);

View file

@ -68,6 +68,7 @@ public:
#endif #endif
// helpers for setting // helpers for setting
static void Clear(Error* errptr);
static void SetErrno(Error* errptr, int err); static void SetErrno(Error* errptr, int err);
static void SetErrno(Error* errptr, std::string_view prefix, int err); static void SetErrno(Error* errptr, std::string_view prefix, int err);
static void SetSocket(Error* errptr, int err); static void SetSocket(Error* errptr, int err);

View file

@ -1112,11 +1112,9 @@ std::unique_ptr<GPUDevice> GPUDevice::CreateDeviceForAPI(RenderAPI api)
} }
#if defined(ENABLE_VULKAN) || defined(__APPLE__) #if defined(ENABLE_VULKAN) || defined(__APPLE__)
#define SHADERC_INIT_FUNCTIONS(X) \
X(shaderc_compiler_initialize) \
X(shaderc_compiler_release)
#define SHADERC_FUNCTIONS(X) \ #define SHADERC_FUNCTIONS(X) \
X(shaderc_compiler_initialize) \
X(shaderc_compiler_release) \
X(shaderc_compile_options_initialize) \ X(shaderc_compile_options_initialize) \
X(shaderc_compile_options_release) \ X(shaderc_compile_options_release) \
X(shaderc_compile_options_set_source_language) \ X(shaderc_compile_options_set_source_language) \
@ -1134,9 +1132,10 @@ std::unique_ptr<GPUDevice> GPUDevice::CreateDeviceForAPI(RenderAPI api)
// TODO: NOT thread safe, yet. // TODO: NOT thread safe, yet.
namespace dyn_shaderc { namespace dyn_shaderc {
static bool Open(); static bool Open();
static void Close();
static DynamicLibrary s_library; static DynamicLibrary s_library;
static std::unique_ptr<struct shaderc_compiler, void (*)(shaderc_compiler_t)> s_compiler(nullptr, nullptr); static shaderc_compiler_t s_compiler = nullptr;
#define ADD_FUNC(F) static decltype(&::F) F; #define ADD_FUNC(F) static decltype(&::F) F;
SHADERC_FUNCTIONS(ADD_FUNC) SHADERC_FUNCTIONS(ADD_FUNC)
@ -1167,27 +1166,40 @@ bool dyn_shaderc::Open()
if (!s_library.GetSymbol(#F, &F)) \ if (!s_library.GetSymbol(#F, &F)) \
{ \ { \
Log_ErrorFmt("Failed to find function {}", #F); \ Log_ErrorFmt("Failed to find function {}", #F); \
s_library.Close(); \ Close(); \
return false; \
}
#define LOAD_INIT_FUNC(F) \
decltype(&::F) p##F; \
if (!s_library.GetSymbol(#F, &p##F)) \
{ \
Log_ErrorFmt("Failed to find function {}", #F); \
s_library.Close(); \
return false; \ return false; \
} }
SHADERC_FUNCTIONS(LOAD_FUNC) SHADERC_FUNCTIONS(LOAD_FUNC)
SHADERC_INIT_FUNCTIONS(LOAD_INIT_FUNC)
#undef LOAD_FUNC #undef LOAD_FUNC
#undef LOAD_INIT_FUNC
s_compiler = decltype(s_compiler)(pshaderc_compiler_initialize(), pshaderc_compiler_release); s_compiler = shaderc_compiler_initialize();
if (!s_compiler)
{
Log_ErrorPrint("shaderc_compiler_initialize() failed");
Close();
return false;
}
std::atexit(&dyn_shaderc::Close);
return true; return true;
} }
void dyn_shaderc::Close()
{
if (s_compiler)
{
shaderc_compiler_release(s_compiler);
s_compiler = nullptr;
}
#define UNLOAD_FUNC(F) F = nullptr;
SHADERC_FUNCTIONS(UNLOAD_FUNC)
#undef UNLOAD_FUNC
s_library.Close();
}
#undef SHADERC_FUNCTIONS #undef SHADERC_FUNCTIONS
#undef SHADERC_INIT_FUNCTIONS #undef SHADERC_INIT_FUNCTIONS
@ -1216,7 +1228,7 @@ bool GPUDevice::CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, std::string_v
shaderc_compilation_result_t result; shaderc_compilation_result_t result;
const shaderc_compilation_status status = dyn_shaderc::shaderc_compile_into_spv( const shaderc_compilation_status status = dyn_shaderc::shaderc_compile_into_spv(
dyn_shaderc::s_compiler.get(), source.data(), source.length(), stage_kinds[static_cast<size_t>(stage)], "source", dyn_shaderc::s_compiler, source.data(), source.length(), stage_kinds[static_cast<size_t>(stage)], "source",
entry_point, options, &result); entry_point, options, &result);
if (status != shaderc_compilation_status_success) if (status != shaderc_compilation_status_success)
{ {

View file

@ -1845,7 +1845,7 @@ GPUDevice::AdapterAndModeList VulkanDevice::StaticGetAdapterAndModeList()
} }
else else
{ {
if (Vulkan::LoadVulkanLibrary()) if (Vulkan::LoadVulkanLibrary(nullptr))
{ {
ScopedGuard lib_guard([]() { Vulkan::UnloadVulkanLibrary(); }); ScopedGuard lib_guard([]() { Vulkan::UnloadVulkanLibrary(); });
OptionalExtensions oe = {}; OptionalExtensions oe = {};
@ -1857,6 +1857,8 @@ GPUDevice::AdapterAndModeList VulkanDevice::StaticGetAdapterAndModeList()
vkDestroyInstance(instance, nullptr); vkDestroyInstance(instance, nullptr);
} }
Vulkan::UnloadVulkanLibrary();
} }
} }
@ -1925,9 +1927,10 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
bool enable_debug_utils = m_debug_device; bool enable_debug_utils = m_debug_device;
bool enable_validation_layer = m_debug_device; bool enable_validation_layer = m_debug_device;
if (!Vulkan::LoadVulkanLibrary()) if (!Vulkan::LoadVulkanLibrary(error))
{ {
Error::SetStringView(error, "Failed to load Vulkan library. Does your GPU and/or driver support Vulkan?"); Error::AddPrefix(error,
"Failed to load Vulkan library. Does your GPU and/or driver support Vulkan?\nThe error was:");
return false; return false;
} }

View file

@ -7,6 +7,7 @@
#include "vulkan_loader.h" #include "vulkan_loader.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/dynamic_library.h"
#include "common/log.h" #include "common/log.h"
#include <cstdarg> #include <cstdarg>
@ -15,14 +16,6 @@
#include <cstring> #include <cstring>
#include <string> #include <string>
#ifndef _WIN32
#include <dlfcn.h>
#endif
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
Log_SetChannel(VulkanDevice); Log_SetChannel(VulkanDevice);
extern "C" { extern "C" {
@ -47,134 +40,51 @@ void Vulkan::ResetVulkanLibraryFunctionPointers()
#undef VULKAN_MODULE_ENTRY_POINT #undef VULKAN_MODULE_ENTRY_POINT
} }
#if defined(_WIN32) static DynamicLibrary s_vulkan_library;
static HMODULE s_vulkan_module;
bool Vulkan::IsVulkanLibraryLoaded() bool Vulkan::IsVulkanLibraryLoaded()
{ {
return s_vulkan_module != NULL; return s_vulkan_library.IsOpen();
} }
bool Vulkan::LoadVulkanLibrary() bool Vulkan::LoadVulkanLibrary(Error* error)
{ {
AssertMsg(!s_vulkan_module, "Vulkan module is not loaded."); AssertMsg(!s_vulkan_library.IsOpen(), "Vulkan module is not loaded.");
s_vulkan_module = LoadLibraryA("vulkan-1.dll"); #ifdef __APPLE__
if (!s_vulkan_module)
{
Log_ErrorPrintf("Failed to load vulkan-1.dll");
return false;
}
bool required_functions_missing = false;
auto LoadFunction = [&](FARPROC* func_ptr, const char* name, bool is_required) {
*func_ptr = GetProcAddress(s_vulkan_module, name);
if (!(*func_ptr) && is_required)
{
Log_ErrorPrintf("Vulkan: Failed to load required module function %s", name);
required_functions_missing = true;
}
};
#define VULKAN_MODULE_ENTRY_POINT(name, required) LoadFunction(reinterpret_cast<FARPROC*>(&name), #name, required);
#include "vulkan_entry_points.inl"
#undef VULKAN_MODULE_ENTRY_POINT
if (required_functions_missing)
{
ResetVulkanLibraryFunctionPointers();
FreeLibrary(s_vulkan_module);
s_vulkan_module = nullptr;
return false;
}
return true;
}
void Vulkan::UnloadVulkanLibrary()
{
ResetVulkanLibraryFunctionPointers();
if (s_vulkan_module)
FreeLibrary(s_vulkan_module);
s_vulkan_module = nullptr;
}
#else
static void* s_vulkan_module;
bool Vulkan::IsVulkanLibraryLoaded()
{
return s_vulkan_module != nullptr;
}
bool Vulkan::LoadVulkanLibrary()
{
AssertMsg(!s_vulkan_module, "Vulkan module is not loaded.");
#if defined(__APPLE__)
// Check if a path to a specific Vulkan library has been specified. // Check if a path to a specific Vulkan library has been specified.
char* libvulkan_env = getenv("LIBVULKAN_PATH"); char* libvulkan_env = getenv("LIBVULKAN_PATH");
if (libvulkan_env) if (libvulkan_env)
s_vulkan_module = dlopen(libvulkan_env, RTLD_NOW); s_vulkan_library.Open(libvulkan_env, error);
if (!s_vulkan_module) if (!s_vulkan_library.IsOpen() &&
!s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("MoltenVK").c_str(), error))
{ {
unsigned path_size = 0; return false;
_NSGetExecutablePath(nullptr, &path_size);
std::string path;
path.resize(path_size);
if (_NSGetExecutablePath(path.data(), &path_size) == 0)
{
path[path_size] = 0;
size_t pos = path.rfind('/');
if (pos != std::string::npos)
{
path.erase(pos);
path += "/../Frameworks/libMoltenVK.dylib";
s_vulkan_module = dlopen(path.c_str(), RTLD_NOW);
} }
}
}
if (!s_vulkan_module)
s_vulkan_module = dlopen("libvulkan.dylib", RTLD_NOW);
#else #else
// Names of libraries to search. Desktop should use libvulkan.so.1 or libvulkan.so. // try versioned first, then unversioned.
static const char* search_lib_names[] = {"libvulkan.so.1", "libvulkan.so"}; if (!s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan", 1).c_str(), error) &&
for (size_t i = 0; i < sizeof(search_lib_names) / sizeof(search_lib_names[0]); i++) !s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan").c_str(), error))
{ {
s_vulkan_module = dlopen(search_lib_names[i], RTLD_NOW); return false;
if (s_vulkan_module)
break;
} }
#endif #endif
if (!s_vulkan_module)
{
Log_ErrorPrintf("Failed to load or locate libvulkan.so");
return false;
}
bool required_functions_missing = false; bool required_functions_missing = false;
auto LoadFunction = [&](void** func_ptr, const char* name, bool is_required) {
*func_ptr = dlsym(s_vulkan_module, name);
if (!(*func_ptr) && is_required)
{
Log_ErrorPrintf("Vulkan: Failed to load required module function %s", name);
required_functions_missing = true;
}
};
#define VULKAN_MODULE_ENTRY_POINT(name, required) LoadFunction(reinterpret_cast<void**>(&name), #name, required); #define VULKAN_MODULE_ENTRY_POINT(name, required) \
if (!s_vulkan_library.GetSymbol(#name, &name)) \
{ \
Log_ErrorFmt("Vulkan: Failed to load required module function {}", #name); \
required_functions_missing = true; \
}
#include "vulkan_entry_points.inl" #include "vulkan_entry_points.inl"
#undef VULKAN_MODULE_ENTRY_POINT #undef VULKAN_MODULE_ENTRY_POINT
if (required_functions_missing) if (required_functions_missing)
{ {
ResetVulkanLibraryFunctionPointers(); ResetVulkanLibraryFunctionPointers();
dlclose(s_vulkan_module); s_vulkan_library.Close();
s_vulkan_module = nullptr;
return false; return false;
} }
@ -184,13 +94,9 @@ bool Vulkan::LoadVulkanLibrary()
void Vulkan::UnloadVulkanLibrary() void Vulkan::UnloadVulkanLibrary()
{ {
ResetVulkanLibraryFunctionPointers(); ResetVulkanLibraryFunctionPointers();
if (s_vulkan_module) s_vulkan_library.Close();
dlclose(s_vulkan_module);
s_vulkan_module = nullptr;
} }
#endif
bool Vulkan::LoadVulkanInstanceFunctions(VkInstance instance) bool Vulkan::LoadVulkanInstanceFunctions(VkInstance instance)
{ {
bool required_functions_missing = false; bool required_functions_missing = false;

View file

@ -1,8 +1,10 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once #pragma once
class Error;
#define VK_NO_PROTOTYPES #define VK_NO_PROTOTYPES
#ifdef _WIN32 #ifdef _WIN32
@ -93,7 +95,7 @@
namespace Vulkan { namespace Vulkan {
bool IsVulkanLibraryLoaded(); bool IsVulkanLibraryLoaded();
bool LoadVulkanLibrary(); bool LoadVulkanLibrary(Error* error);
bool LoadVulkanInstanceFunctions(VkInstance instance); bool LoadVulkanInstanceFunctions(VkInstance instance);
bool LoadVulkanDeviceFunctions(VkDevice device); bool LoadVulkanDeviceFunctions(VkDevice device);
void UnloadVulkanLibrary(); void UnloadVulkanLibrary();