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