Files
FEX-Emu--FEX/ThunkLibs/libvulkan/Guest.cpp
T
Ryan Houdek b89ebd931e Thunks: Fixes symbol conflict between GL and Vulkan
Fixes crash that occurs in applications that use both GL and Vulkan,
Like UE5 Vulkan native games. Fixes Ender Magnolia.

The issue here is that UE5 loads libGL first, which initializes our
libGL thunks, setting its X11Manager's functions.

It then loads libvulkan, which calls our oninit constructor, which
because of the symbol conflict, calls in to the libGL thunk's host
functions to reinitialize its function pointers, never initializing the
Vulkan X11Manager's functions. It would then crash as soon as an X11
function was used.

Give them unique symbol names so we don't accidentally look up the
incorrect symbol.
2025-11-01 16:48:56 -07:00

96 lines
3.4 KiB
C++

/*
$info$
tags: thunklibs|Vulkan
$end_info$
*/
#define VK_USE_64_BIT_PTR_DEFINES 0
#define VK_USE_PLATFORM_XLIB_XRANDR_EXT
#define VK_USE_PLATFORM_XLIB_KHR
#define VK_USE_PLATFORM_XCB_KHR
#define VK_USE_PLATFORM_WAYLAND_KHR
#include <vulkan/vulkan.h>
#include "common/Guest.h"
#include <cstdio>
#include <dlfcn.h>
#include <functional>
#include <string_view>
#include <unordered_map>
#include "thunkgen_guest_libvulkan.inl"
extern "C" {
// Maps Vulkan API function names to the address of a guest function which is
// linked to the corresponding host function pointer
const std::unordered_map<std::string_view, uintptr_t /* guest function address */> HostPtrInvokers = std::invoke([]() {
#define PAIR(name, unused) Ret[#name] = reinterpret_cast<uintptr_t>(GetCallerForHostFunction(name));
std::unordered_map<std::string_view, uintptr_t> Ret;
FOREACH_internal_SYMBOL(PAIR);
return Ret;
#undef PAIR
});
// This variable controls the behavior of vkGetDevice/InstanceProcAddr for functions we don't know the signature of:
// - if false (default), we return a nullptr (since the application might have a fallback code path)
// - if true, we return a stub function that fatally errors upon being called
constexpr bool stub_unknown_functions = false;
// Fatally erroring function with a thunk-like interface. This is used as a placeholder for unknown Vulkan functions
[[noreturn]]
static void FatalError(void* raw_args) {
auto called_function = reinterpret_cast<PackedArguments<void, uintptr_t>*>(raw_args)->a0;
fprintf(stderr, "FATAL: Called unknown Vulkan function at address %p\n", reinterpret_cast<void*>(called_function));
__builtin_trap();
}
static PFN_vkVoidFunction MakeGuestCallable(const char* origin, PFN_vkVoidFunction func, const char* name) {
auto It = HostPtrInvokers.find(name);
if (It == HostPtrInvokers.end()) {
fprintf(stderr, "%s: Unknown Vulkan function at address %p: %s\n", origin, func, name);
if (stub_unknown_functions) {
const auto StubHostPtrInvoker = CallHostFunction<FatalError, void>;
LinkAddressToFunction((uintptr_t)func, reinterpret_cast<uintptr_t>(StubHostPtrInvoker));
return func;
}
return nullptr;
}
fprintf(stderr, "Linking address %p to host invoker %#zx\n", func, It->second);
LinkAddressToFunction((uintptr_t)func, It->second);
return func;
}
PFN_vkVoidFunction vkGetDeviceProcAddr(VkDevice a_0, const char* a_1) {
auto Ret = fexfn_pack_vkGetDeviceProcAddr(a_0, a_1);
if (!Ret) {
return nullptr;
}
return MakeGuestCallable(__FUNCTION__, Ret, a_1);
}
PFN_vkVoidFunction vkGetInstanceProcAddr(VkInstance a_0, const char* a_1) {
if (a_1 == std::string_view {"vkGetDeviceProcAddr"}) {
return (PFN_vkVoidFunction)vkGetDeviceProcAddr;
} else {
auto Ret = fexfn_pack_vkGetInstanceProcAddr(a_0, a_1);
if (!Ret) {
return nullptr;
}
return MakeGuestCallable(__FUNCTION__, Ret, a_1);
}
}
}
void OnInit() {
// TODO: Load libX11 on-demand instead
void* libx11 = dlopen("libX11.so.6", RTLD_LAZY);
fexfn_pack_Vulkan_SetGuestXSync((uintptr_t)dlsym(libx11, "XSync"), (uintptr_t)CallbackUnpack<decltype(XSync)>::Unpack);
fexfn_pack_Vulkan_SetGuestXGetVisualInfo((uintptr_t)dlsym(libx11, "XGetVisualInfo"), (uintptr_t)CallbackUnpack<decltype(XGetVisualInfo)>::Unpack);
fexfn_pack_Vulkan_SetGuestXDisplayString((uintptr_t)dlsym(libx11, "XDisplayString"), (uintptr_t)CallbackUnpack<decltype(XDisplayString)>::Unpack);
}
LOAD_LIB_INIT(libvulkan, OnInit)