1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_X11)
24
25#include "../SDL_vulkan_internal.h"
26
27#include "SDL_x11video.h"
28
29#include "SDL_x11vulkan.h"
30
31#include <X11/Xlib.h>
32// #include <xcb/xcb.h>
33
34#ifdef SDL_PLATFORM_OPENBSD
35#define DEFAULT_VULKAN "libvulkan.so"
36#define DEFAULT_X11_XCB "libX11-xcb.so"
37#else
38#define DEFAULT_VULKAN "libvulkan.so.1"
39#define DEFAULT_X11_XCB "libX11-xcb.so.1"
40#endif
41
42/*
43typedef uint32_t xcb_window_t;
44typedef uint32_t xcb_visualid_t;
45*/
46
47bool X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
48{
49 SDL_VideoData *videoData = _this->internal;
50 VkExtensionProperties *extensions = NULL;
51 Uint32 extensionCount = 0;
52 bool hasSurfaceExtension = false;
53 bool hasXlibSurfaceExtension = false;
54 bool hasXCBSurfaceExtension = false;
55 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
56 Uint32 i;
57 if (_this->vulkan_config.loader_handle) {
58 return SDL_SetError("Vulkan already loaded");
59 }
60
61 // Load the Vulkan loader library
62 if (!path) {
63 path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY);
64 }
65 if (!path) {
66 path = DEFAULT_VULKAN;
67 }
68 _this->vulkan_config.loader_handle = SDL_LoadObject(path);
69 if (!_this->vulkan_config.loader_handle) {
70 return false;
71 }
72 SDL_strlcpy(_this->vulkan_config.loader_path, path, SDL_arraysize(_this->vulkan_config.loader_path));
73 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction(
74 _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr");
75 if (!vkGetInstanceProcAddr) {
76 goto fail;
77 }
78 _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr;
79 _this->vulkan_config.vkEnumerateInstanceExtensionProperties =
80 (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)(
81 VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
82 if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) {
83 goto fail;
84 }
85 extensions = SDL_Vulkan_CreateInstanceExtensionsList(
86 (PFN_vkEnumerateInstanceExtensionProperties)
87 _this->vulkan_config.vkEnumerateInstanceExtensionProperties,
88 &extensionCount);
89 if (!extensions) {
90 goto fail;
91 }
92 for (i = 0; i < extensionCount; i++) {
93 if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
94 hasSurfaceExtension = true;
95 } else if (SDL_strcmp(VK_KHR_XCB_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
96 hasXCBSurfaceExtension = true;
97 } else if (SDL_strcmp(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
98 hasXlibSurfaceExtension = true;
99 }
100 }
101 SDL_free(extensions);
102 if (!hasSurfaceExtension) {
103 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension");
104 goto fail;
105 }
106 if (hasXlibSurfaceExtension) {
107 videoData->vulkan_xlib_xcb_library = NULL;
108 } else if (!hasXCBSurfaceExtension) {
109 SDL_SetError("Installed Vulkan doesn't implement either the " VK_KHR_XCB_SURFACE_EXTENSION_NAME "extension or the " VK_KHR_XLIB_SURFACE_EXTENSION_NAME " extension");
110 goto fail;
111 } else {
112 const char *libX11XCBLibraryName = SDL_GetHint(SDL_HINT_X11_XCB_LIBRARY);
113 if (!libX11XCBLibraryName || !*libX11XCBLibraryName) {
114 libX11XCBLibraryName = DEFAULT_X11_XCB;
115 }
116 videoData->vulkan_xlib_xcb_library = SDL_LoadObject(libX11XCBLibraryName);
117 if (!videoData->vulkan_xlib_xcb_library) {
118 goto fail;
119 }
120 videoData->vulkan_XGetXCBConnection =
121 (PFN_XGetXCBConnection)SDL_LoadFunction(videoData->vulkan_xlib_xcb_library, "XGetXCBConnection");
122 if (!videoData->vulkan_XGetXCBConnection) {
123 SDL_UnloadObject(videoData->vulkan_xlib_xcb_library);
124 goto fail;
125 }
126 }
127 return true;
128
129fail:
130 SDL_UnloadObject(_this->vulkan_config.loader_handle);
131 _this->vulkan_config.loader_handle = NULL;
132 return false;
133}
134
135void X11_Vulkan_UnloadLibrary(SDL_VideoDevice *_this)
136{
137 SDL_VideoData *videoData = _this->internal;
138 if (_this->vulkan_config.loader_handle) {
139 if (videoData->vulkan_xlib_xcb_library) {
140 SDL_UnloadObject(videoData->vulkan_xlib_xcb_library);
141 }
142 SDL_UnloadObject(_this->vulkan_config.loader_handle);
143 _this->vulkan_config.loader_handle = NULL;
144 }
145}
146
147char const* const* X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
148 Uint32 *count)
149{
150 SDL_VideoData *videoData = _this->internal;
151 if (videoData->vulkan_xlib_xcb_library) {
152 static const char *const extensionsForXCB[] = {
153 VK_KHR_SURFACE_EXTENSION_NAME,
154 VK_KHR_XCB_SURFACE_EXTENSION_NAME,
155 };
156 if(count) {
157 *count = SDL_arraysize(extensionsForXCB);
158 }
159 return extensionsForXCB;
160 } else {
161 static const char *const extensionsForXlib[] = {
162 VK_KHR_SURFACE_EXTENSION_NAME,
163 VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
164 };
165 if(count) {
166 *count = SDL_arraysize(extensionsForXlib);
167 }
168 return extensionsForXlib;
169 }
170}
171
172bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this,
173 SDL_Window *window,
174 VkInstance instance,
175 const struct VkAllocationCallbacks *allocator,
176 VkSurfaceKHR *surface)
177{
178 SDL_VideoData *videoData = _this->internal;
179 SDL_WindowData *windowData = window->internal;
180 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
181 if (!_this->vulkan_config.loader_handle) {
182 return SDL_SetError("Vulkan is not loaded");
183 }
184 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
185 if (videoData->vulkan_xlib_xcb_library) {
186 PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR =
187 (PFN_vkCreateXcbSurfaceKHR)vkGetInstanceProcAddr(instance,
188 "vkCreateXcbSurfaceKHR");
189 VkXcbSurfaceCreateInfoKHR createInfo;
190 VkResult result;
191 if (!vkCreateXcbSurfaceKHR) {
192 return SDL_SetError(VK_KHR_XCB_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance.");
193 }
194 SDL_zero(createInfo);
195 createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
196 createInfo.connection = videoData->vulkan_XGetXCBConnection(videoData->display);
197 if (!createInfo.connection) {
198 return SDL_SetError("XGetXCBConnection failed");
199 }
200 createInfo.window = (xcb_window_t)windowData->xwindow;
201 result = vkCreateXcbSurfaceKHR(instance, &createInfo, allocator, surface);
202 if (result != VK_SUCCESS) {
203 return SDL_SetError("vkCreateXcbSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result));
204 }
205 } else {
206 PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR =
207 (PFN_vkCreateXlibSurfaceKHR)vkGetInstanceProcAddr(instance,
208 "vkCreateXlibSurfaceKHR");
209 VkXlibSurfaceCreateInfoKHR createInfo;
210 VkResult result;
211 if (!vkCreateXlibSurfaceKHR) {
212 return SDL_SetError(VK_KHR_XLIB_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance.");
213 }
214 SDL_zero(createInfo);
215 createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
216 createInfo.dpy = videoData->display;
217 createInfo.window = (xcb_window_t)windowData->xwindow;
218 result = vkCreateXlibSurfaceKHR(instance, &createInfo, allocator, surface);
219 if (result != VK_SUCCESS) {
220 return SDL_SetError("vkCreateXlibSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result));
221 }
222 }
223
224 return true; // success!
225}
226
227void X11_Vulkan_DestroySurface(SDL_VideoDevice *_this,
228 VkInstance instance,
229 VkSurfaceKHR surface,
230 const struct VkAllocationCallbacks *allocator)
231{
232 if (_this->vulkan_config.loader_handle) {
233 SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
234 }
235}
236
237bool X11_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this,
238 VkInstance instance,
239 VkPhysicalDevice physicalDevice,
240 Uint32 queueFamilyIndex)
241{
242 SDL_VideoData *videoData = _this->internal;
243 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
244 const char *forced_visual_id;
245 VisualID visualid;
246
247 if (!_this->vulkan_config.loader_handle) {
248 return SDL_SetError("Vulkan is not loaded");
249 }
250 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
251
252 forced_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_WINDOW_VISUALID);
253 if (forced_visual_id) {
254 visualid = SDL_strtol(forced_visual_id, NULL, 0);
255 } else {
256 visualid = X11_XVisualIDFromVisual(DefaultVisual(videoData->display, DefaultScreen(videoData->display)));
257 }
258
259 if (videoData->vulkan_xlib_xcb_library) {
260 PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR =
261 (PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)vkGetInstanceProcAddr(
262 instance,
263 "vkGetPhysicalDeviceXcbPresentationSupportKHR");
264
265 if (!vkGetPhysicalDeviceXcbPresentationSupportKHR) {
266 return SDL_SetError(VK_KHR_XCB_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance.");
267 }
268
269 return vkGetPhysicalDeviceXcbPresentationSupportKHR(physicalDevice,
270 queueFamilyIndex,
271 videoData->vulkan_XGetXCBConnection(videoData->display),
272 visualid);
273 } else {
274 PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR =
275 (PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR)vkGetInstanceProcAddr(
276 instance,
277 "vkGetPhysicalDeviceXlibPresentationSupportKHR");
278
279 if (!vkGetPhysicalDeviceXlibPresentationSupportKHR) {
280 return SDL_SetError(VK_KHR_XLIB_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance.");
281 }
282
283 return vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice,
284 queueFamilyIndex,
285 videoData->display,
286 visualid);
287 }
288}
289
290#endif
291