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