[Fix] (DirectVulkan): Xlib surface fallback for ICDs without VK_EXT_headless_surface

Real drivers (NVIDIA proprietary Linux) don't implement VK_EXT_headless_surface,
which the pbuffer path required unconditionally, hard-aborting at CreateInstance.
CreateInstance now detects instance-extension support and requests
VK_KHR_xlib_surface instead when headless is unavailable; CreateSurface creates
an unmapped Xlib window purely to obtain a VkSurfaceKHR, then proceeds through
the existing swapchain path unchanged. Shutdown destroys the window it owns.
Lavapipe and other headless-capable ICDs are unaffected.
This commit is contained in:
BZLZHH
2026-08-01 06:38:30 -04:00
parent dd745d7547
commit d18c6a1bae
2 changed files with 79 additions and 1 deletions
@@ -2866,6 +2866,15 @@ void main() {
#if defined(VK_USE_PLATFORM_XLIB_KHR)
if (m_platformDisplay != nullptr) {
if (m_ownsFallbackXlibWindow && m_window != 0 && m_platformLibrary != nullptr) {
using XDestroyWindowFn = int (*)(Display*, Window);
auto* destroyWindow = reinterpret_cast<XDestroyWindowFn>(dlsym(m_platformLibrary, "XDestroyWindow"));
if (destroyWindow) {
destroyWindow(static_cast<Display*>(m_platformDisplay), static_cast<Window>(m_window));
}
m_window = 0;
m_ownsFallbackXlibWindow = false;
}
using XCloseDisplayFn = int (*)(Display*);
auto* closeDisplay = reinterpret_cast<XCloseDisplayFn>(m_platformCloseDisplay);
if (closeDisplay) {
@@ -9284,6 +9293,18 @@ void main() {
if (!m_window) {
#ifdef VK_USE_PLATFORM_METAL_EXT
exts.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
#elif defined VK_USE_PLATFORM_XLIB_KHR
m_headlessSurfaceSupported = IsExtensionSupported(m_extensions, VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
if (m_headlessSurfaceSupported) {
exts.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
} else {
// Real ICDs (e.g. NVIDIA's proprietary Linux driver) may not implement
// VK_EXT_headless_surface at all. CreateSurface() falls back to a
// hidden Xlib window in that case, so request that extension instead.
MGLOG_I("%s not available; falling back to a hidden %s surface for the pbuffer context.",
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
exts.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
}
#else
exts.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
#endif
@@ -9985,6 +10006,21 @@ void main() {
m_window = reinterpret_cast<NativeWindowType>(
CreateInternalMetalLayer(m_config.SurfaceWidth, m_config.SurfaceHeight, &m_platformDisplay));
m_platformLibrary = reinterpret_cast<void*>(m_window);
#elif defined VK_USE_PLATFORM_XLIB_KHR
if (m_headlessSurfaceSupported) {
auto* createHeadlessSurface =
reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
MOBILEGL_ASSERT(createHeadlessSurface != nullptr,
"VK_EXT_headless_surface is not available for DirectVulkan pbuffer surface");
VkHeadlessSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT};
VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface),
"vkCreateHeadlessSurfaceEXT failed");
return;
}
// VK_EXT_headless_surface unavailable on this ICD: fall through to the
// Xlib branch below, which creates a hidden window since m_window is
// still null here.
#else
auto* createHeadlessSurface =
reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
@@ -10019,7 +10055,8 @@ void main() {
sci.pLayer = reinterpret_cast<const void*>(m_window);
VK_VERIFY(vkCreateMetalSurfaceEXT(m_instance, &sci, nullptr, &m_surface), "vkCreateMetalSurfaceEXT failed");
#elif defined VK_USE_PLATFORM_XLIB_KHR
MOBILEGL_ASSERT(m_window, "X11 Window is null");
// m_window may legitimately still be null here: the pbuffer/headless-fallback
// path below creates its own window when the caller didn't provide one.
void* x11Lib = dlopen("libX11.so.6", RTLD_LOCAL | RTLD_NOW);
if (!x11Lib) {
@@ -10039,6 +10076,38 @@ void main() {
m_platformLibrary = x11Lib;
m_platformCloseDisplay = reinterpret_cast<void*>(xCloseDisplay);
if (!m_window) {
// Pbuffer/headless fallback: the ICD didn't implement
// VK_EXT_headless_surface (e.g. NVIDIA's proprietary Linux driver), so
// CreateInstance() requested VK_KHR_xlib_surface instead and left
// m_window null for us to fill in here. This window is never mapped -
// it exists only to give the WSI a valid drawable - so nothing is ever
// shown on screen; the swapchain image backs the GL default framebuffer
// exactly like the headless-surface path does.
using XDefaultRootWindowFn = Window (*)(Display*);
using XDefaultScreenFn = int (*)(Display*);
using XBlackPixelFn = unsigned long (*)(Display*, int);
using XCreateSimpleWindowFn = Window (*)(Display*, Window, int, int, unsigned int, unsigned int,
unsigned int, unsigned long, unsigned long);
auto* xDefaultRootWindow = reinterpret_cast<XDefaultRootWindowFn>(dlsym(x11Lib, "XDefaultRootWindow"));
auto* xDefaultScreen = reinterpret_cast<XDefaultScreenFn>(dlsym(x11Lib, "XDefaultScreen"));
auto* xBlackPixel = reinterpret_cast<XBlackPixelFn>(dlsym(x11Lib, "XBlackPixel"));
auto* xCreateSimpleWindow =
reinterpret_cast<XCreateSimpleWindowFn>(dlsym(x11Lib, "XCreateSimpleWindow"));
MOBILEGL_ASSERT(xDefaultRootWindow && xDefaultScreen && xBlackPixel && xCreateSimpleWindow,
"Failed to resolve XCreateSimpleWindow dependencies for the Xlib pbuffer fallback");
const int screen = xDefaultScreen(display);
const Uint32 width = std::max<Uint32>(m_config.SurfaceWidth, 1);
const Uint32 height = std::max<Uint32>(m_config.SurfaceHeight, 1);
const Window fallbackWindow = xCreateSimpleWindow(display, xDefaultRootWindow(display), 0, 0, width,
height, 0, xBlackPixel(display, screen),
xBlackPixel(display, screen));
MOBILEGL_ASSERT(fallbackWindow != 0, "XCreateSimpleWindow failed for the Xlib pbuffer fallback");
m_window = static_cast<NativeWindowType>(fallbackWindow);
m_ownsFallbackXlibWindow = true;
}
VkXlibSurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR};
sci.dpy = display;
sci.window = static_cast<Window>(m_window);
@@ -431,6 +431,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void* m_platformDisplay = nullptr;
void* m_platformLibrary = nullptr;
void* m_platformCloseDisplay = nullptr;
// Some real ICDs (e.g. NVIDIA's proprietary Linux driver) don't implement
// VK_EXT_headless_surface at all. Detected once in CreateInstance() from the
// enumerated instance extensions; when false, CreateSurface() falls back to a
// hidden Xlib window instead of vkCreateHeadlessSurfaceEXT.
Bool m_headlessSurfaceSupported = true;
// Set when CreateSurface() had to create its own Xlib window for the fallback
// above (rather than being handed one by the caller), so Shutdown() knows it
// owns that window and must destroy it.
Bool m_ownsFallbackXlibWindow = false;
VulkanRendererConfig m_config;
Bool m_swapchainResizeRequested = false;
// Presentation is suspended while the window is zero-area (minimized): the