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
22/*
23 * @author Manuel Alfayate Corchere <redwindwanderer@gmail.com>.
24 * Based on Jacob Lifshay's SDL_x11vulkan.c.
25 */
26
27#include "SDL_internal.h"
28
29#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_KMSDRM)
30
31#include "../SDL_vulkan_internal.h"
32
33#include "SDL_kmsdrmvideo.h"
34#include "SDL_kmsdrmdyn.h"
35#include "SDL_kmsdrmvulkan.h"
36
37#include <sys/ioctl.h>
38
39#ifdef SDL_PLATFORM_OPENBSD
40#define DEFAULT_VULKAN "libvulkan.so"
41#else
42#define DEFAULT_VULKAN "libvulkan.so.1"
43#endif
44
45bool KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
46{
47 VkExtensionProperties *extensions = NULL;
48 Uint32 i, extensionCount = 0;
49 bool hasSurfaceExtension = false;
50 bool hasDisplayExtension = false;
51 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
52
53 if (_this->vulkan_config.loader_handle) {
54 return SDL_SetError("Vulkan already loaded");
55 }
56
57 // Load the Vulkan library
58 if (!path) {
59 path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY);
60 }
61 if (!path) {
62 path = DEFAULT_VULKAN;
63 }
64
65 _this->vulkan_config.loader_handle = SDL_LoadObject(path);
66
67 if (!_this->vulkan_config.loader_handle) {
68 return false;
69 }
70
71 SDL_strlcpy(_this->vulkan_config.loader_path, path,
72 SDL_arraysize(_this->vulkan_config.loader_path));
73
74 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction(
75 _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr");
76
77 if (!vkGetInstanceProcAddr) {
78 goto fail;
79 }
80
81 _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr;
82 _this->vulkan_config.vkEnumerateInstanceExtensionProperties =
83 (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)(
84 VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
85
86 if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) {
87 goto fail;
88 }
89
90 extensions = SDL_Vulkan_CreateInstanceExtensionsList(
91 (PFN_vkEnumerateInstanceExtensionProperties)
92 _this->vulkan_config.vkEnumerateInstanceExtensionProperties,
93 &extensionCount);
94
95 if (!extensions) {
96 goto fail;
97 }
98
99 for (i = 0; i < extensionCount; i++) {
100 if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
101 hasSurfaceExtension = true;
102 } else if (SDL_strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, extensions[i].extensionName) == 0) {
103 hasDisplayExtension = true;
104 }
105 }
106
107 SDL_free(extensions);
108
109 if (!hasSurfaceExtension) {
110 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension");
111 goto fail;
112 } else if (!hasDisplayExtension) {
113 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_DISPLAY_EXTENSION_NAME "extension");
114 goto fail;
115 }
116
117 return true;
118
119fail:
120 SDL_UnloadObject(_this->vulkan_config.loader_handle);
121 _this->vulkan_config.loader_handle = NULL;
122 return false;
123}
124
125void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this)
126{
127 if (_this->vulkan_config.loader_handle) {
128 SDL_UnloadObject(_this->vulkan_config.loader_handle);
129 _this->vulkan_config.loader_handle = NULL;
130 }
131}
132
133/*********************************************************************/
134// Here we can put whatever Vulkan extensions we want to be enabled
135// at instance creation, which is done in the programs, not in SDL.
136// So: programs call SDL_Vulkan_GetInstanceExtensions() and here
137// we put the extensions specific to this backend so the programs
138// get a list with the extension we want, so they can include that
139// list in the ppEnabledExtensionNames and EnabledExtensionCount
140// members of the VkInstanceCreateInfo struct passed to
141// vkCreateInstance().
142/*********************************************************************/
143char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
144 Uint32 *count)
145{
146 static const char *const extensionsForKMSDRM[] = {
147 VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME
148 };
149 if (count) {
150 *count = SDL_arraysize(extensionsForKMSDRM);
151 }
152 return extensionsForKMSDRM;
153}
154
155/***********************************************************************/
156// First thing to know is that we don't call vkCreateInstance() here.
157// Instead, programs using SDL and Vulkan create their Vulkan instance
158// and we get it here, ready to use.
159// Extensions specific for this platform are activated in
160// KMSDRM_Vulkan_GetInstanceExtensions(), like we do with
161// VK_KHR_DISPLAY_EXTENSION_NAME, which is what we need for x-less VK.
162/***********************************************************************/
163bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
164 SDL_Window *window,
165 VkInstance instance,
166 const struct VkAllocationCallbacks *allocator,
167 VkSurfaceKHR *surface)
168{
169 VkPhysicalDevice gpu = NULL;
170 uint32_t gpu_count;
171 uint32_t display_count;
172 uint32_t mode_count;
173 uint32_t plane_count;
174 uint32_t plane = UINT32_MAX;
175
176 VkPhysicalDevice *physical_devices = NULL;
177 VkPhysicalDeviceProperties *device_props = NULL;
178 VkDisplayPropertiesKHR *display_props = NULL;
179 VkDisplayModePropertiesKHR *mode_props = NULL;
180 VkDisplayPlanePropertiesKHR *plane_props = NULL;
181 VkDisplayPlaneCapabilitiesKHR plane_caps;
182
183 VkDisplayModeCreateInfoKHR display_mode_create_info;
184 VkDisplaySurfaceCreateInfoKHR display_plane_surface_create_info;
185
186 VkExtent2D image_size;
187 VkDisplayKHR display;
188 VkDisplayModeKHR display_mode = (VkDisplayModeKHR)0;
189 VkDisplayModePropertiesKHR display_mode_props = { 0 };
190 VkDisplayModeParametersKHR new_mode_parameters = { { 0, 0 }, 0 };
191 // Prefer a plane that supports per-pixel alpha.
192 VkDisplayPlaneAlphaFlagBitsKHR alpha_mode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
193
194 VkResult result;
195 bool ret = false;
196 bool valid_gpu = false;
197 bool mode_found = false;
198 bool plane_supports_display = false;
199
200 // Get the display index from the display being used by the window.
201 int display_index = SDL_GetDisplayIndex(SDL_GetDisplayForWindow(window));
202 int i, j;
203
204 // Get the function pointers for the functions we will use.
205 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
206 (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
207
208 PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR =
209 (PFN_vkCreateDisplayPlaneSurfaceKHR)vkGetInstanceProcAddr(
210 instance, "vkCreateDisplayPlaneSurfaceKHR");
211
212 PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices =
213 (PFN_vkEnumeratePhysicalDevices)vkGetInstanceProcAddr(
214 instance, "vkEnumeratePhysicalDevices");
215
216 PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties =
217 (PFN_vkGetPhysicalDeviceProperties)vkGetInstanceProcAddr(
218 instance, "vkGetPhysicalDeviceProperties");
219
220 PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR =
221 (PFN_vkGetPhysicalDeviceDisplayPropertiesKHR)vkGetInstanceProcAddr(
222 instance, "vkGetPhysicalDeviceDisplayPropertiesKHR");
223
224 PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR =
225 (PFN_vkGetDisplayModePropertiesKHR)vkGetInstanceProcAddr(
226 instance, "vkGetDisplayModePropertiesKHR");
227
228 PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR =
229 (PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR)vkGetInstanceProcAddr(
230 instance, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR");
231
232 PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR =
233 (PFN_vkGetDisplayPlaneSupportedDisplaysKHR)vkGetInstanceProcAddr(
234 instance, "vkGetDisplayPlaneSupportedDisplaysKHR");
235
236 PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR =
237 (PFN_vkGetDisplayPlaneCapabilitiesKHR)vkGetInstanceProcAddr(
238 instance, "vkGetDisplayPlaneCapabilitiesKHR");
239
240 PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR =
241 (PFN_vkCreateDisplayModeKHR)vkGetInstanceProcAddr(
242 instance, "vkCreateDisplayModeKHR");
243
244 if (!_this->vulkan_config.loader_handle) {
245 SDL_SetError("Vulkan is not loaded");
246 goto clean;
247 }
248
249 /*************************************/
250 // Block for vulkan surface creation
251 /*************************************/
252
253 /****************************************************************/
254 // If we got vkCreateDisplayPlaneSurfaceKHR() pointer, it means
255 // that the VK_KHR_Display extension is active on the instance.
256 // That's the central extension we need for x-less VK!
257 /****************************************************************/
258 if (!vkCreateDisplayPlaneSurfaceKHR) {
259 SDL_SetError(VK_KHR_DISPLAY_EXTENSION_NAME
260 " extension is not enabled in the Vulkan instance.");
261 goto clean;
262 }
263
264 /* A GPU (or physical_device, in vkcube terms) is a physical GPU.
265 A machine with more than one video output doesn't need to have more than one GPU,
266 like the Pi4 which has 1 GPU and 2 video outputs.
267 Just in case, we test that the GPU we choose is Vulkan-capable.
268 If there are new reports about VK init failures, hardcode
269 gpu = physical_devices[0], instead of probing, and go with that.
270 */
271
272 // Get the physical device count.
273 vkEnumeratePhysicalDevices(instance, &gpu_count, NULL);
274
275 if (gpu_count == 0) {
276 SDL_SetError("Vulkan can't find physical devices (gpus).");
277 goto clean;
278 }
279
280 // Get the physical devices.
281 physical_devices = SDL_malloc(sizeof(VkPhysicalDevice) * gpu_count);
282 device_props = SDL_malloc(sizeof(VkPhysicalDeviceProperties));
283 vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices);
284
285 // Iterate on the physical devices.
286 for (i = 0; i < gpu_count; i++) {
287
288 // Get the physical device properties.
289 vkGetPhysicalDeviceProperties(
290 physical_devices[i],
291 device_props);
292
293 // Is this device a real GPU that supports API version 1 at least?
294 if (device_props->apiVersion >= 1 &&
295 (device_props->deviceType == 1 || device_props->deviceType == 2)) {
296 gpu = physical_devices[i];
297 valid_gpu = true;
298 break;
299 }
300 }
301
302 if (!valid_gpu) {
303 SDL_SetError("Vulkan can't find a valid physical device (gpu).");
304 goto clean;
305 }
306
307 /* A display is a video output. 1 GPU can have N displays.
308 Vulkan only counts the connected displays.
309 Get the display count of the GPU. */
310 vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, &display_count, NULL);
311 if (display_count == 0) {
312 SDL_SetError("Vulkan can't find any displays.");
313 goto clean;
314 }
315
316 // Get the props of the displays of the physical device.
317 display_props = (VkDisplayPropertiesKHR *)SDL_malloc(display_count * sizeof(*display_props));
318 vkGetPhysicalDeviceDisplayPropertiesKHR(gpu,
319 &display_count,
320 display_props);
321
322 // Get the chosen display based on the display index.
323 display = display_props[display_index].display;
324
325 // Get the list of the display videomodes.
326 vkGetDisplayModePropertiesKHR(gpu,
327 display,
328 &mode_count, NULL);
329
330 if (mode_count == 0) {
331 SDL_SetError("Vulkan can't find any video modes for display %i (%s)", 0,
332 display_props[display_index].displayName);
333 goto clean;
334 }
335
336 mode_props = (VkDisplayModePropertiesKHR *)SDL_malloc(mode_count * sizeof(*mode_props));
337 vkGetDisplayModePropertiesKHR(gpu,
338 display,
339 &mode_count, mode_props);
340
341 /* Get a video mode equal to the window size among the predefined ones,
342 if possible.
343 REMEMBER: We have to get a small enough videomode for the window size,
344 because videomode determines how big the scanout region is and we can't
345 scanout a region bigger than the window (we would be reading past the
346 buffer, and Vulkan would give us a confusing VK_ERROR_SURFACE_LOST_KHR). */
347 for (i = 0; i < mode_count; i++) {
348 if (mode_props[i].parameters.visibleRegion.width == window->w &&
349 mode_props[i].parameters.visibleRegion.height == window->h) {
350 display_mode_props = mode_props[i];
351 mode_found = true;
352 break;
353 }
354 }
355
356 if (mode_found &&
357 display_mode_props.parameters.visibleRegion.width > 0 &&
358 display_mode_props.parameters.visibleRegion.height > 0) {
359 // Found a suitable mode among the predefined ones. Use that.
360 display_mode = display_mode_props.displayMode;
361 } else {
362
363 /* Couldn't find a suitable mode among the predefined ones, so try to create our own.
364 This won't work for some video chips atm (like Pi's VideoCore) so these are limited
365 to supported resolutions. Don't try to use "closest" resolutions either, because
366 those are often bigger than the window size, thus causing out-of-bunds scanout. */
367 new_mode_parameters.visibleRegion.width = window->w;
368 new_mode_parameters.visibleRegion.height = window->h;
369 /* SDL (and DRM, if we look at drmModeModeInfo vrefresh) uses plain integer Hz for
370 display mode refresh rate, but Vulkan expects higher precision. */
371 new_mode_parameters.refreshRate = (uint32_t)(window->current_fullscreen_mode.refresh_rate * 1000);
372
373 SDL_zero(display_mode_create_info);
374 display_mode_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR;
375 display_mode_create_info.parameters = new_mode_parameters;
376 result = vkCreateDisplayModeKHR(gpu,
377 display,
378 &display_mode_create_info,
379 NULL, &display_mode);
380 if (result != VK_SUCCESS) {
381 SDL_SetError("Vulkan couldn't find a predefined mode for that window size and couldn't create a suitable mode.");
382 goto clean;
383 }
384 }
385
386 // Just in case we get here without a display_mode.
387 if (!display_mode) {
388 SDL_SetError("Vulkan couldn't get a display mode.");
389 goto clean;
390 }
391
392 // Get the list of the physical device planes.
393 vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, NULL);
394 if (plane_count == 0) {
395 SDL_SetError("Vulkan can't find any planes.");
396 goto clean;
397 }
398 plane_props = SDL_malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count);
399 vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, plane_props);
400
401 /* Iterate on the list of planes of the physical device
402 to find a plane that matches these criteria:
403 -It must be compatible with the chosen display + mode.
404 -It isn't currently bound to another display.
405 -It supports per-pixel alpha, if possible. */
406 for (i = 0; i < plane_count; i++) {
407
408 uint32_t supported_displays_count = 0;
409 VkDisplayKHR *supported_displays;
410
411 // See if the plane is compatible with the current display.
412 vkGetDisplayPlaneSupportedDisplaysKHR(gpu, i, &supported_displays_count, NULL);
413 if (supported_displays_count == 0) {
414 // This plane doesn't support any displays. Continue to the next plane.
415 continue;
416 }
417
418 // Get the list of displays supported by this plane.
419 supported_displays = (VkDisplayKHR *)SDL_malloc(sizeof(VkDisplayKHR) * supported_displays_count);
420 vkGetDisplayPlaneSupportedDisplaysKHR(gpu, i,
421 &supported_displays_count, supported_displays);
422
423 /* The plane must be bound to the chosen display, or not in use.
424 If none of these is true, iterate to another plane. */
425 if (!((plane_props[i].currentDisplay == display) || (plane_props[i].currentDisplay == VK_NULL_HANDLE))) {
426 continue;
427 }
428
429 /* Iterate the list of displays supported by this plane
430 in order to find out if the chosen display is among them. */
431 plane_supports_display = false;
432 for (j = 0; j < supported_displays_count; j++) {
433 if (supported_displays[j] == display) {
434 plane_supports_display = true;
435 break;
436 }
437 }
438
439 // Free the list of displays supported by this plane.
440 if (supported_displays) {
441 SDL_free(supported_displays);
442 }
443
444 // If the display is not supported by this plane, iterate to the next plane.
445 if (!plane_supports_display) {
446 continue;
447 }
448
449 // Want a plane that supports the alpha mode we have chosen.
450 vkGetDisplayPlaneCapabilitiesKHR(gpu, display_mode, i, &plane_caps);
451 if (plane_caps.supportedAlpha == alpha_mode) {
452 // Yep, this plane is alright.
453 plane = i;
454 break;
455 }
456 }
457
458 // If we couldn't find an appropriate plane, error out.
459 if (plane == UINT32_MAX) {
460 SDL_SetError("Vulkan couldn't find an appropriate plane.");
461 goto clean;
462 }
463
464 /********************************************/
465 // Let's finally create the Vulkan surface!
466 /********************************************/
467
468 image_size.width = window->w;
469 image_size.height = window->h;
470
471 SDL_zero(display_plane_surface_create_info);
472 display_plane_surface_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
473 display_plane_surface_create_info.displayMode = display_mode;
474 display_plane_surface_create_info.planeIndex = plane;
475 display_plane_surface_create_info.imageExtent = image_size;
476 display_plane_surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
477 display_plane_surface_create_info.alphaMode = alpha_mode;
478 result = vkCreateDisplayPlaneSurfaceKHR(instance,
479 &display_plane_surface_create_info,
480 allocator,
481 surface);
482 if (result != VK_SUCCESS) {
483 SDL_SetError("vkCreateDisplayPlaneSurfaceKHR failed: %s",
484 SDL_Vulkan_GetResultString(result));
485 goto clean;
486 }
487
488 ret = true; // success!
489
490clean:
491 if (physical_devices) {
492 SDL_free(physical_devices);
493 }
494 if (display_props) {
495 SDL_free(display_props);
496 }
497 if (device_props) {
498 SDL_free(device_props);
499 }
500 if (plane_props) {
501 SDL_free(plane_props);
502 }
503 if (mode_props) {
504 SDL_free(mode_props);
505 }
506
507 return ret;
508}
509
510void KMSDRM_Vulkan_DestroySurface(SDL_VideoDevice *_this,
511 VkInstance instance,
512 VkSurfaceKHR surface,
513 const struct VkAllocationCallbacks *allocator)
514{
515 if (_this->vulkan_config.loader_handle) {
516 SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
517 }
518}
519
520#endif
521