1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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#include "SDL_vulkan_internal.h"
24#include "SDL_error.h"
25
26#if SDL_VIDEO_VULKAN
27
28const char *SDL_Vulkan_GetResultString(VkResult result)
29{
30 switch ((int)result) {
31 case VK_SUCCESS:
32 return "VK_SUCCESS";
33 case VK_NOT_READY:
34 return "VK_NOT_READY";
35 case VK_TIMEOUT:
36 return "VK_TIMEOUT";
37 case VK_EVENT_SET:
38 return "VK_EVENT_SET";
39 case VK_EVENT_RESET:
40 return "VK_EVENT_RESET";
41 case VK_INCOMPLETE:
42 return "VK_INCOMPLETE";
43 case VK_ERROR_OUT_OF_HOST_MEMORY:
44 return "VK_ERROR_OUT_OF_HOST_MEMORY";
45 case VK_ERROR_OUT_OF_DEVICE_MEMORY:
46 return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
47 case VK_ERROR_INITIALIZATION_FAILED:
48 return "VK_ERROR_INITIALIZATION_FAILED";
49 case VK_ERROR_DEVICE_LOST:
50 return "VK_ERROR_DEVICE_LOST";
51 case VK_ERROR_MEMORY_MAP_FAILED:
52 return "VK_ERROR_MEMORY_MAP_FAILED";
53 case VK_ERROR_LAYER_NOT_PRESENT:
54 return "VK_ERROR_LAYER_NOT_PRESENT";
55 case VK_ERROR_EXTENSION_NOT_PRESENT:
56 return "VK_ERROR_EXTENSION_NOT_PRESENT";
57 case VK_ERROR_FEATURE_NOT_PRESENT:
58 return "VK_ERROR_FEATURE_NOT_PRESENT";
59 case VK_ERROR_INCOMPATIBLE_DRIVER:
60 return "VK_ERROR_INCOMPATIBLE_DRIVER";
61 case VK_ERROR_TOO_MANY_OBJECTS:
62 return "VK_ERROR_TOO_MANY_OBJECTS";
63 case VK_ERROR_FORMAT_NOT_SUPPORTED:
64 return "VK_ERROR_FORMAT_NOT_SUPPORTED";
65 case VK_ERROR_FRAGMENTED_POOL:
66 return "VK_ERROR_FRAGMENTED_POOL";
67 case VK_ERROR_UNKNOWN:
68 return "VK_ERROR_UNKNOWN";
69 case VK_ERROR_OUT_OF_POOL_MEMORY:
70 return "VK_ERROR_OUT_OF_POOL_MEMORY";
71 case VK_ERROR_INVALID_EXTERNAL_HANDLE:
72 return "VK_ERROR_INVALID_EXTERNAL_HANDLE";
73 case VK_ERROR_FRAGMENTATION:
74 return "VK_ERROR_FRAGMENTATION";
75 case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS:
76 return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS";
77 case VK_ERROR_SURFACE_LOST_KHR:
78 return "VK_ERROR_SURFACE_LOST_KHR";
79 case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
80 return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
81 case VK_SUBOPTIMAL_KHR:
82 return "VK_SUBOPTIMAL_KHR";
83 case VK_ERROR_OUT_OF_DATE_KHR:
84 return "VK_ERROR_OUT_OF_DATE_KHR";
85 case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
86 return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
87 case VK_ERROR_VALIDATION_FAILED_EXT:
88 return "VK_ERROR_VALIDATION_FAILED_EXT";
89 case VK_ERROR_INVALID_SHADER_NV:
90 return "VK_ERROR_INVALID_SHADER_NV";
91 case VK_ERROR_INCOMPATIBLE_VERSION_KHR:
92 return "VK_ERROR_INCOMPATIBLE_VERSION_KHR";
93 case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT:
94 return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
95 case VK_ERROR_NOT_PERMITTED_EXT:
96 return "VK_ERROR_NOT_PERMITTED_EXT";
97 case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
98 return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
99 case VK_THREAD_IDLE_KHR:
100 return "VK_THREAD_IDLE_KHR";
101 case VK_THREAD_DONE_KHR:
102 return "VK_THREAD_DONE_KHR";
103 case VK_OPERATION_DEFERRED_KHR:
104 return "VK_OPERATION_DEFERRED_KHR";
105 case VK_OPERATION_NOT_DEFERRED_KHR:
106 return "VK_OPERATION_NOT_DEFERRED_KHR";
107 case VK_PIPELINE_COMPILE_REQUIRED_EXT:
108 return "VK_PIPELINE_COMPILE_REQUIRED_EXT";
109 default:
110 break;
111 }
112 if (result < 0) {
113 return "VK_ERROR_<Unknown>";
114 }
115 return "VK_<Unknown>";
116}
117
118VkExtensionProperties *SDL_Vulkan_CreateInstanceExtensionsList(
119 PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties,
120 Uint32 *extensionCount)
121{
122 Uint32 count = 0;
123 VkResult result = vkEnumerateInstanceExtensionProperties(NULL, &count, NULL);
124 VkExtensionProperties *retval;
125
126 if (result == VK_ERROR_INCOMPATIBLE_DRIVER) {
127 /* Avoid the ERR_MAX_STRLEN limit by passing part of the message as a string argument. */
128 SDL_SetError(
129 "You probably don't have a working Vulkan driver installed. %s %s %s(%d)",
130 "Getting Vulkan extensions failed:",
131 "vkEnumerateInstanceExtensionProperties returned",
132 SDL_Vulkan_GetResultString(result),
133 (int)result);
134 return NULL;
135 } else if (result != VK_SUCCESS) {
136 SDL_SetError(
137 "Getting Vulkan extensions failed: vkEnumerateInstanceExtensionProperties returned "
138 "%s(%d)",
139 SDL_Vulkan_GetResultString(result),
140 (int)result);
141 return NULL;
142 }
143
144 if (count == 0) {
145 retval = SDL_calloc(1, sizeof(VkExtensionProperties)); // so we can return non-null
146 } else {
147 retval = SDL_calloc(count, sizeof(VkExtensionProperties));
148 }
149
150 if (!retval) {
151 SDL_OutOfMemory();
152 return NULL;
153 }
154
155 result = vkEnumerateInstanceExtensionProperties(NULL, &count, retval);
156 if (result != VK_SUCCESS) {
157 SDL_SetError(
158 "Getting Vulkan extensions failed: vkEnumerateInstanceExtensionProperties returned "
159 "%s(%d)",
160 SDL_Vulkan_GetResultString(result),
161 (int)result);
162 SDL_free(retval);
163 return NULL;
164 }
165 *extensionCount = count;
166 return retval;
167}
168
169SDL_bool SDL_Vulkan_GetInstanceExtensions_Helper(unsigned *userCount,
170 const char **userNames,
171 unsigned nameCount,
172 const char *const *names)
173{
174 if (userNames) {
175 unsigned i;
176
177 if (*userCount < nameCount) {
178 SDL_SetError("Output array for SDL_Vulkan_GetInstanceExtensions needs to be at least %d big", nameCount);
179 return SDL_FALSE;
180 }
181
182 for (i = 0; i < nameCount; i++) {
183 userNames[i] = names[i];
184 }
185 }
186 *userCount = nameCount;
187 return SDL_TRUE;
188}
189
190/* Alpha modes, in order of preference */
191static const VkDisplayPlaneAlphaFlagBitsKHR alphaModes[4] = {
192 VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR,
193 VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR,
194 VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR,
195 VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR,
196};
197
198SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_,
199 VkInstance instance,
200 VkSurfaceKHR *surface)
201{
202 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
203 (PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr_;
204#define VULKAN_INSTANCE_FUNCTION(name) \
205 PFN_##name name = (PFN_##name)vkGetInstanceProcAddr((VkInstance)instance, #name)
206 VULKAN_INSTANCE_FUNCTION(vkEnumeratePhysicalDevices);
207 VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceDisplayPropertiesKHR);
208 VULKAN_INSTANCE_FUNCTION(vkGetDisplayModePropertiesKHR);
209 VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceDisplayPlanePropertiesKHR);
210 VULKAN_INSTANCE_FUNCTION(vkGetDisplayPlaneCapabilitiesKHR);
211 VULKAN_INSTANCE_FUNCTION(vkGetDisplayPlaneSupportedDisplaysKHR);
212 VULKAN_INSTANCE_FUNCTION(vkCreateDisplayPlaneSurfaceKHR);
213#undef VULKAN_INSTANCE_FUNCTION
214 VkDisplaySurfaceCreateInfoKHR createInfo;
215 VkResult result;
216 uint32_t physicalDeviceCount = 0;
217 VkPhysicalDevice *physicalDevices = NULL;
218 uint32_t physicalDeviceIndex;
219 const char *chosenDisplayId;
220 int displayId = 0; /* Counting from physical device 0, display 0 */
221
222 if (!vkEnumeratePhysicalDevices ||
223 !vkGetPhysicalDeviceDisplayPropertiesKHR ||
224 !vkGetDisplayModePropertiesKHR ||
225 !vkGetPhysicalDeviceDisplayPlanePropertiesKHR ||
226 !vkGetDisplayPlaneCapabilitiesKHR ||
227 !vkGetDisplayPlaneSupportedDisplaysKHR ||
228 !vkCreateDisplayPlaneSurfaceKHR) {
229 SDL_SetError(VK_KHR_DISPLAY_EXTENSION_NAME " extension is not enabled in the Vulkan instance.");
230 goto error;
231 }
232
233 if ((chosenDisplayId = SDL_getenv("SDL_VULKAN_DISPLAY")) != NULL) {
234 displayId = SDL_atoi(chosenDisplayId);
235 }
236
237 /* Enumerate physical devices */
238 result = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, NULL);
239 if (result != VK_SUCCESS) {
240 SDL_SetError("Could not enumerate Vulkan physical devices");
241 goto error;
242 }
243
244 if (physicalDeviceCount == 0) {
245 SDL_SetError("No Vulkan physical devices");
246 goto error;
247 }
248
249 physicalDevices = SDL_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount);
250 if (!physicalDevices) {
251 SDL_OutOfMemory();
252 goto error;
253 }
254
255 result = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices);
256 if (result != VK_SUCCESS) {
257 SDL_SetError("Error enumerating physical devices");
258 goto error;
259 }
260
261 for (physicalDeviceIndex = 0; physicalDeviceIndex < physicalDeviceCount; physicalDeviceIndex++) {
262 VkPhysicalDevice physicalDevice = physicalDevices[physicalDeviceIndex];
263 uint32_t displayPropertiesCount = 0;
264 VkDisplayPropertiesKHR *displayProperties = NULL;
265 uint32_t displayModePropertiesCount = 0;
266 VkDisplayModePropertiesKHR *displayModeProperties = NULL;
267 int bestMatchIndex = -1;
268 uint32_t refreshRate = 0;
269 uint32_t i;
270 uint32_t displayPlanePropertiesCount = 0;
271 int planeIndex = -1;
272 VkDisplayKHR display;
273 VkDisplayPlanePropertiesKHR *displayPlaneProperties = NULL;
274 VkExtent2D extent;
275 VkDisplayPlaneCapabilitiesKHR planeCaps;
276
277 /* Get information about the physical displays */
278 result = vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertiesCount, NULL);
279 if (result != VK_SUCCESS || displayPropertiesCount == 0) {
280 /* This device has no physical device display properties, move on to next. */
281 continue;
282 }
283 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of display properties for device %u: %u",
284 physicalDeviceIndex, displayPropertiesCount);
285
286 if (displayId < 0 || (uint32_t) displayId >= displayPropertiesCount) {
287 /* Display id specified was higher than number of available displays, move to next physical device. */
288 displayId -= displayPropertiesCount;
289 continue;
290 }
291
292 displayProperties = SDL_malloc(sizeof(VkDisplayPropertiesKHR) * displayPropertiesCount);
293 if (!displayProperties) {
294 SDL_OutOfMemory();
295 goto error;
296 }
297
298 result = vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertiesCount, displayProperties);
299 if (result != VK_SUCCESS || displayPropertiesCount == 0) {
300 SDL_free(displayProperties);
301 SDL_SetError("Error enumerating physical device displays");
302 goto error;
303 }
304
305 display = displayProperties[displayId].display;
306 extent = displayProperties[displayId].physicalResolution;
307 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Display: %s Native resolution: %ux%u",
308 displayProperties[displayId].displayName, extent.width, extent.height);
309
310 SDL_free(displayProperties);
311 displayProperties = NULL;
312
313 /* Get display mode properties for the chosen display */
314 result = vkGetDisplayModePropertiesKHR(physicalDevice, display, &displayModePropertiesCount, NULL);
315 if (result != VK_SUCCESS || displayModePropertiesCount == 0)
316 {
317 SDL_SetError("Error enumerating display modes");
318 goto error;
319 }
320 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of display modes: %u", displayModePropertiesCount);
321
322 displayModeProperties = SDL_malloc(sizeof(VkDisplayModePropertiesKHR) * displayModePropertiesCount);
323 if (!displayModeProperties) {
324 SDL_OutOfMemory();
325 goto error;
326 }
327
328 result = vkGetDisplayModePropertiesKHR(physicalDevice, display, &displayModePropertiesCount, displayModeProperties);
329 if (result != VK_SUCCESS || displayModePropertiesCount == 0) {
330 SDL_SetError("Error enumerating display modes");
331 SDL_free(displayModeProperties);
332 goto error;
333 }
334
335 /* Try to find a display mode that matches the native resolution */
336 for (i = 0; i < displayModePropertiesCount; ++i) {
337 if (displayModeProperties[i].parameters.visibleRegion.width == extent.width &&
338 displayModeProperties[i].parameters.visibleRegion.height == extent.height &&
339 displayModeProperties[i].parameters.refreshRate > refreshRate) {
340 bestMatchIndex = i;
341 refreshRate = displayModeProperties[i].parameters.refreshRate;
342 }
343 }
344
345 if (bestMatchIndex < 0) {
346 SDL_SetError("Found no matching display mode");
347 SDL_free(displayModeProperties);
348 goto error;
349 }
350
351 SDL_zero(createInfo);
352 createInfo.displayMode = displayModeProperties[bestMatchIndex].displayMode;
353 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Matching mode %ux%u with refresh rate %u",
354 displayModeProperties[bestMatchIndex].parameters.visibleRegion.width,
355 displayModeProperties[bestMatchIndex].parameters.visibleRegion.height,
356 refreshRate);
357
358 SDL_free(displayModeProperties);
359 displayModeProperties = NULL;
360
361 /* Try to find a plane index that supports our display */
362 result = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &displayPlanePropertiesCount, NULL);
363 if (result != VK_SUCCESS || displayPlanePropertiesCount == 0) {
364 SDL_SetError("Error enumerating display planes");
365 goto error;
366 }
367 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of display planes: %u", displayPlanePropertiesCount);
368
369 displayPlaneProperties = SDL_malloc(sizeof(VkDisplayPlanePropertiesKHR) * displayPlanePropertiesCount);
370 if (!displayPlaneProperties) {
371 SDL_OutOfMemory();
372 goto error;
373 }
374
375 result = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &displayPlanePropertiesCount, displayPlaneProperties);
376 if (result != VK_SUCCESS || displayPlanePropertiesCount == 0) {
377 SDL_SetError("Error enumerating display plane properties");
378 SDL_free(displayPlaneProperties);
379 goto error;
380 }
381
382 for (i = 0; i < displayPlanePropertiesCount; ++i) {
383 uint32_t planeSupportedDisplaysCount = 0;
384 VkDisplayKHR *planeSupportedDisplays = NULL;
385 uint32_t j;
386
387 /* Check if plane is attached to a display, if not, continue. */
388 if (displayPlaneProperties[i].currentDisplay == VK_NULL_HANDLE) {
389 continue;
390 }
391
392 /* Check supported displays for this plane. */
393 result = vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, i, &planeSupportedDisplaysCount, NULL);
394 if (result != VK_SUCCESS || planeSupportedDisplaysCount == 0) {
395 continue; /* No supported displays, on to next plane. */
396 }
397
398 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of supported displays for plane %u: %u", i, planeSupportedDisplaysCount);
399
400 planeSupportedDisplays = SDL_malloc(sizeof(VkDisplayKHR) * planeSupportedDisplaysCount);
401 if (!planeSupportedDisplays) {
402 SDL_free(displayPlaneProperties);
403 SDL_OutOfMemory();
404 goto error;
405 }
406
407 result = vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, i, &planeSupportedDisplaysCount, planeSupportedDisplays);
408 if (result != VK_SUCCESS || planeSupportedDisplaysCount == 0) {
409 SDL_SetError("Error enumerating supported displays, or no supported displays");
410 SDL_free(planeSupportedDisplays);
411 SDL_free(displayPlaneProperties);
412 goto error;
413 }
414
415 for (j = 0; j < planeSupportedDisplaysCount && planeSupportedDisplays[j] != display; ++j)
416 {
417 }
418
419 SDL_free(planeSupportedDisplays);
420 planeSupportedDisplays = NULL;
421
422 if (j == planeSupportedDisplaysCount) {
423 /* This display is not supported for this plane, move on. */
424 continue;
425 }
426
427 result = vkGetDisplayPlaneCapabilitiesKHR(physicalDevice, createInfo.displayMode, i, &planeCaps);
428 if (result != VK_SUCCESS) {
429 SDL_SetError("Error getting display plane capabilities");
430 SDL_free(displayPlaneProperties);
431 goto error;
432 }
433
434 /* Check if plane fulfills extent requirements. */
435 if (extent.width >= planeCaps.minDstExtent.width && extent.height >= planeCaps.minDstExtent.height &&
436 extent.width <= planeCaps.maxDstExtent.width && extent.height <= planeCaps.maxDstExtent.height) {
437 /* If it does, choose this plane. */
438 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Choosing plane %d, minimum extent %dx%d maximum extent %dx%d", i,
439 planeCaps.minDstExtent.width, planeCaps.minDstExtent.height,
440 planeCaps.maxDstExtent.width, planeCaps.maxDstExtent.height);
441 planeIndex = i;
442 break;
443 }
444 }
445
446 if (planeIndex < 0) {
447 SDL_SetError("No plane supports the selected resolution");
448 SDL_free(displayPlaneProperties);
449 goto error;
450 }
451
452 createInfo.planeIndex = planeIndex;
453 createInfo.planeStackIndex = displayPlaneProperties[planeIndex].currentStackIndex;
454 SDL_free(displayPlaneProperties);
455 displayPlaneProperties = NULL;
456
457 /* Find a supported alpha mode. Not all planes support OPAQUE */
458 createInfo.alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
459 for (i = 0; i < SDL_arraysize(alphaModes); i++) {
460 if (planeCaps.supportedAlpha & alphaModes[i]) {
461 createInfo.alphaMode = alphaModes[i];
462 break;
463 }
464 }
465 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Chose alpha mode 0x%x", createInfo.alphaMode);
466
467 /* Found a match, finally! Fill in extent, and break from loop */
468 createInfo.imageExtent = extent;
469 break;
470 }
471
472 SDL_free(physicalDevices);
473 physicalDevices = NULL;
474
475 if (physicalDeviceIndex == physicalDeviceCount) {
476 SDL_SetError("No usable displays found or requested display out of range");
477 return SDL_FALSE;
478 }
479
480 createInfo.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
481 createInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
482 createInfo.globalAlpha = 1.0f;
483
484 result = vkCreateDisplayPlaneSurfaceKHR(instance, &createInfo, NULL, surface);
485 if (result != VK_SUCCESS) {
486 SDL_SetError("vkCreateDisplayPlaneSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result));
487 return SDL_FALSE;
488 }
489 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Created surface");
490 return SDL_TRUE;
491
492error:
493 SDL_free(physicalDevices);
494 return SDL_FALSE;
495}
496
497#endif
498
499/* vi: set ts=4 sw=4 expandtab: */
500