SDL_kmsdrmvulkan.c (15488B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 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 SDL_VIDEO_VULKAN && SDL_VIDEO_DRIVER_KMSDRM 30 31 #include "SDL_kmsdrmvideo.h" 32 #include "SDL_kmsdrmdyn.h" 33 #include "SDL_assert.h" 34 35 #include "SDL_loadso.h" 36 #include "SDL_kmsdrmvulkan.h" 37 #include "SDL_syswm.h" 38 #include "sys/ioctl.h" 39 40 #if defined(__OpenBSD__) 41 #define DEFAULT_VULKAN "libvulkan.so" 42 #else 43 #define DEFAULT_VULKAN "libvulkan.so.1" 44 #endif 45 46 int KMSDRM_Vulkan_LoadLibrary(_THIS, const char *path) 47 { 48 VkExtensionProperties *extensions = NULL; 49 Uint32 i, extensionCount = 0; 50 SDL_bool hasSurfaceExtension = SDL_FALSE; 51 SDL_bool hasDisplayExtension = SDL_FALSE; 52 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; 53 54 if(_this->vulkan_config.loader_handle) 55 return SDL_SetError("Vulkan already loaded"); 56 57 /* Load the Vulkan library */ 58 if(!path) 59 path = SDL_getenv("SDL_VULKAN_LIBRARY"); 60 if(!path) 61 path = DEFAULT_VULKAN; 62 63 _this->vulkan_config.loader_handle = SDL_LoadObject(path); 64 65 if(!_this->vulkan_config.loader_handle) 66 return -1; 67 68 SDL_strlcpy(_this->vulkan_config.loader_path, path, 69 SDL_arraysize(_this->vulkan_config.loader_path)); 70 71 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction( 72 _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr"); 73 74 if(!vkGetInstanceProcAddr) 75 goto fail; 76 77 _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr; 78 _this->vulkan_config.vkEnumerateInstanceExtensionProperties = 79 (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)( 80 VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); 81 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 90 if(!extensions) 91 goto fail; 92 93 for(i = 0; i < extensionCount; i++) 94 { 95 if(SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) 96 hasSurfaceExtension = SDL_TRUE; 97 else if(SDL_strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, extensions[i].extensionName) == 0) 98 hasDisplayExtension = SDL_TRUE; 99 } 100 101 SDL_free(extensions); 102 103 if(!hasSurfaceExtension) 104 { 105 SDL_SetError("Installed Vulkan doesn't implement the " 106 VK_KHR_SURFACE_EXTENSION_NAME " extension"); 107 goto fail; 108 } 109 else if(!hasDisplayExtension) 110 { 111 SDL_SetError("Installed Vulkan doesn't implement the " 112 VK_KHR_DISPLAY_EXTENSION_NAME "extension"); 113 goto fail; 114 } 115 116 return 0; 117 118 fail: 119 SDL_UnloadObject(_this->vulkan_config.loader_handle); 120 _this->vulkan_config.loader_handle = NULL; 121 return -1; 122 } 123 124 void KMSDRM_Vulkan_UnloadLibrary(_THIS) 125 { 126 if(_this->vulkan_config.loader_handle) 127 { 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 /*********************************************************************/ 143 SDL_bool KMSDRM_Vulkan_GetInstanceExtensions(_THIS, 144 SDL_Window *window, 145 unsigned *count, 146 const char **names) 147 { 148 static const char *const extensionsForKMSDRM[] = { 149 VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME 150 }; 151 if(!_this->vulkan_config.loader_handle) 152 { 153 SDL_SetError("Vulkan is not loaded"); 154 return SDL_FALSE; 155 } 156 return SDL_Vulkan_GetInstanceExtensions_Helper( 157 count, names, SDL_arraysize(extensionsForKMSDRM), 158 extensionsForKMSDRM); 159 } 160 161 void KMSDRM_Vulkan_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h) 162 { 163 if (w) { 164 *w = window->w; 165 } 166 167 if (h) { 168 *h = window->h; 169 } 170 } 171 172 /***********************************************************************/ 173 /* First thing to know is that we don't call vkCreateInstance() here. */ 174 /* Instead, programs using SDL and Vulkan create their Vulkan instance */ 175 /* and we get it here, ready to use. */ 176 /* Extensions specific for this platform are activated in */ 177 /* KMSDRM_Vulkan_GetInstanceExtensions(), like we do with */ 178 /* VK_KHR_DISPLAY_EXTENSION_NAME, which is what we need for x-less VK. */ 179 /***********************************************************************/ 180 SDL_bool KMSDRM_Vulkan_CreateSurface(_THIS, 181 SDL_Window *window, 182 VkInstance instance, 183 VkSurfaceKHR *surface) 184 { 185 VkPhysicalDevice gpu; 186 uint32_t gpu_count; 187 uint32_t display_count; 188 uint32_t mode_count; 189 uint32_t plane_count; 190 191 VkPhysicalDevice *physical_devices = NULL; 192 VkDisplayPropertiesKHR *displays_props = NULL; 193 VkDisplayModePropertiesKHR *modes_props = NULL; 194 VkDisplayPlanePropertiesKHR *planes_props = NULL; 195 196 VkDisplayModeCreateInfoKHR display_mode_create_info; 197 VkDisplaySurfaceCreateInfoKHR display_plane_surface_create_info; 198 199 VkExtent2D image_size; 200 VkDisplayModeKHR display_mode; 201 VkDisplayModePropertiesKHR display_mode_props = {0}; 202 203 VkResult result; 204 SDL_bool ret = SDL_FALSE; 205 206 /* We don't receive a display index in KMSDRM_CreateDevice(), only 207 a device index, which determines the GPU to use, but not the output. 208 So we simply use the first connected output (ie, the first connected 209 video output) for now. 210 In other words, change this index to select a different output. Easy! */ 211 int display_index = 0; 212 213 int i; 214 215 SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); 216 217 /* Get the function pointers for the functions we will use. */ 218 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = 219 (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; 220 221 PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR = 222 (PFN_vkCreateDisplayPlaneSurfaceKHR)vkGetInstanceProcAddr( 223 instance, "vkCreateDisplayPlaneSurfaceKHR"); 224 225 PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = 226 (PFN_vkEnumeratePhysicalDevices)vkGetInstanceProcAddr( 227 instance, "vkEnumeratePhysicalDevices"); 228 229 PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR = 230 (PFN_vkGetPhysicalDeviceDisplayPropertiesKHR)vkGetInstanceProcAddr( 231 instance, "vkGetPhysicalDeviceDisplayPropertiesKHR"); 232 233 PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR = 234 (PFN_vkGetDisplayModePropertiesKHR)vkGetInstanceProcAddr( 235 instance, "vkGetDisplayModePropertiesKHR"); 236 237 PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR = 238 (PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR)vkGetInstanceProcAddr( 239 instance, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR"); 240 241 /*PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR = 242 (PFN_vkGetDisplayPlaneSupportedDisplaysKHR)vkGetInstanceProcAddr( 243 instance, "vkGetDisplayPlaneSupportedDisplaysKHR"); 244 245 PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR = 246 (PFN_vkGetDisplayPlaneCapabilitiesKHR)vkGetInstanceProcAddr( 247 instance, "vkGetDisplayPlaneCapabilitiesKHR"); 248 */ 249 250 PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR = 251 (PFN_vkCreateDisplayModeKHR)vkGetInstanceProcAddr( 252 instance, "vkCreateDisplayModeKHR"); 253 254 if(!_this->vulkan_config.loader_handle) 255 { 256 SDL_SetError("Vulkan is not loaded"); 257 goto clean; 258 } 259 260 /*************************************/ 261 /* Block for vulkan surface creation */ 262 /*************************************/ 263 264 /****************************************************************/ 265 /* If we got vkCreateDisplayPlaneSurfaceKHR() pointer, it means */ 266 /* that the VK_KHR_Display extension is active on the instance. */ 267 /* That's the central extension we need for x-less VK! */ 268 /****************************************************************/ 269 if(!vkCreateDisplayPlaneSurfaceKHR) 270 { 271 SDL_SetError(VK_KHR_DISPLAY_EXTENSION_NAME 272 " extension is not enabled in the Vulkan instance."); 273 goto clean; 274 } 275 276 /* Get the physical device count. */ 277 vkEnumeratePhysicalDevices(instance, &gpu_count, NULL); 278 279 if (gpu_count == 0) { 280 SDL_SetError("Vulkan can't find physical devices (gpus)."); 281 goto clean; 282 } 283 284 /* Get the physical devices. */ 285 physical_devices = malloc(sizeof(VkPhysicalDevice) * gpu_count); 286 vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices); 287 288 /* A GPU (or physical_device, in vkcube terms) is a GPU. A machine with more 289 than one video output doen't need to have more than one GPU, like the Pi4 290 which has 1 GPU and 2 video outputs. 291 We grab the GPU/physical_device with the index we got in KMSDR_CreateDevice(). */ 292 gpu = physical_devices[viddata->devindex]; 293 294 /* A display is a video output. 1 GPU can have N displays. 295 Vulkan only counts the connected displays. 296 Get the display count of the GPU. */ 297 vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, &display_count, NULL); 298 if (display_count == 0) { 299 SDL_SetError("Vulkan can't find any displays."); 300 goto clean; 301 } 302 303 /* Get the props of the displays of the physical device. */ 304 displays_props = (VkDisplayPropertiesKHR *) malloc(display_count * sizeof(*displays_props)); 305 vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, 306 &display_count, 307 displays_props); 308 309 /* Get the videomode count for the first display. */ 310 vkGetDisplayModePropertiesKHR(gpu, 311 displays_props[display_index].display, 312 &mode_count, NULL); 313 314 if (mode_count == 0) { 315 SDL_SetError("Vulkan can't find any video modes for display %i (%s)\n", 0, 316 displays_props[display_index].displayName); 317 goto clean; 318 } 319 320 /* Get the props of the videomodes for the first display. */ 321 modes_props = (VkDisplayModePropertiesKHR *) malloc(mode_count * sizeof(*modes_props)); 322 vkGetDisplayModePropertiesKHR(gpu, 323 displays_props[display_index].display, 324 &mode_count, modes_props); 325 326 /* Get the planes count of the physical device. */ 327 vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, NULL); 328 if (plane_count == 0) { 329 SDL_SetError("Vulkan can't find any planes."); 330 goto clean; 331 } 332 333 /* Get the props of the planes for the physical device. */ 334 planes_props = malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count); 335 vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, planes_props); 336 337 /* Get a video mode equal or smaller than the window size. REMEMBER: 338 We have to get a small enough videomode for the window size, 339 because videomode determines how big the scanout region is and we can't 340 scanout a region bigger than the window (we would be reading past the 341 buffer, and Vulkan would give us a confusing VK_ERROR_SURFACE_LOST_KHR). */ 342 for (i = 0; i < mode_count; i++) { 343 if (modes_props[i].parameters.visibleRegion.width <= window->w && 344 modes_props[i].parameters.visibleRegion.height <= window->h) 345 { 346 display_mode_props = modes_props[i]; 347 break; 348 } 349 } 350 351 if (display_mode_props.parameters.visibleRegion.width == 0 352 || display_mode_props.parameters.visibleRegion.height == 0) 353 { 354 SDL_SetError("Vulkan can't find a proper display mode for the window size."); 355 goto clean; 356 } 357 358 /* We have the props of the display mode, but we need an actual display mode. */ 359 display_mode_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR; 360 display_mode_create_info.parameters = display_mode_props.parameters; 361 result = vkCreateDisplayModeKHR(gpu, 362 displays_props[display_index].display, 363 &display_mode_create_info, 364 NULL, &display_mode); 365 if (result != VK_SUCCESS) { 366 SDL_SetError("Vulkan can't create the display mode."); 367 goto clean; 368 } 369 370 /* Let's finally create the Vulkan surface! */ 371 372 image_size.width = window->w; 373 image_size.height = window->h; 374 375 display_plane_surface_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR; 376 display_plane_surface_create_info.displayMode = display_mode; 377 /* For now, simply use the first plane. */ 378 display_plane_surface_create_info.planeIndex = 0; 379 display_plane_surface_create_info.imageExtent = image_size; 380 result = vkCreateDisplayPlaneSurfaceKHR(instance, 381 &display_plane_surface_create_info, 382 NULL, 383 surface); 384 if(result != VK_SUCCESS) 385 { 386 SDL_SetError("vkCreateKMSDRMSurfaceKHR failed: %s", 387 SDL_Vulkan_GetResultString(result)); 388 goto clean; 389 } 390 391 ret = SDL_TRUE; 392 393 clean: 394 if (physical_devices) 395 free (physical_devices); 396 if (displays_props) 397 free (displays_props); 398 if (planes_props) 399 free (planes_props); 400 if (modes_props) 401 free (modes_props); 402 403 return ret; 404 } 405 406 #endif 407 408 /* vim: set ts=4 sw=4 expandtab: */