diff options
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/testvulkan.c')
-rw-r--r-- | src/contrib/SDL-3.2.20/test/testvulkan.c | 1170 |
1 files changed, 1170 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/test/testvulkan.c b/src/contrib/SDL-3.2.20/test/testvulkan.c new file mode 100644 index 0000000..a728b79 --- /dev/null +++ b/src/contrib/SDL-3.2.20/test/testvulkan.c | |||
@@ -0,0 +1,1170 @@ | |||
1 | /* | ||
2 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
3 | |||
4 | This software is provided 'as-is', without any express or implied | ||
5 | warranty. In no event will the authors be held liable for any damages | ||
6 | arising from the use of this software. | ||
7 | |||
8 | Permission is granted to anyone to use this software for any purpose, | ||
9 | including commercial applications, and to alter it and redistribute it | ||
10 | freely. | ||
11 | */ | ||
12 | #include <stdlib.h> | ||
13 | |||
14 | #include <SDL3/SDL_test_common.h> | ||
15 | #include <SDL3/SDL_main.h> | ||
16 | |||
17 | #if defined(SDL_PLATFORM_ANDROID) && defined(__ARM_EABI__) && !defined(__ARM_ARCH_7A__) | ||
18 | |||
19 | int main(int argc, char *argv[]) | ||
20 | { | ||
21 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "No Vulkan support on this system"); | ||
22 | return 1; | ||
23 | } | ||
24 | |||
25 | #else | ||
26 | |||
27 | #define VK_NO_PROTOTYPES | ||
28 | #ifdef HAVE_VULKAN_H | ||
29 | #include <vulkan/vulkan.h> | ||
30 | #else | ||
31 | /* SDL includes a copy for building on systems without the Vulkan SDK */ | ||
32 | #include "../src/video/khronos/vulkan/vulkan.h" | ||
33 | #endif | ||
34 | #include <SDL3/SDL_vulkan.h> | ||
35 | |||
36 | #ifndef UINT64_MAX /* VS2008 */ | ||
37 | #define UINT64_MAX 18446744073709551615 | ||
38 | #endif | ||
39 | |||
40 | #define VULKAN_FUNCTIONS() \ | ||
41 | VULKAN_DEVICE_FUNCTION(vkAcquireNextImageKHR) \ | ||
42 | VULKAN_DEVICE_FUNCTION(vkAllocateCommandBuffers) \ | ||
43 | VULKAN_DEVICE_FUNCTION(vkBeginCommandBuffer) \ | ||
44 | VULKAN_DEVICE_FUNCTION(vkCmdClearColorImage) \ | ||
45 | VULKAN_DEVICE_FUNCTION(vkCmdPipelineBarrier) \ | ||
46 | VULKAN_DEVICE_FUNCTION(vkCreateCommandPool) \ | ||
47 | VULKAN_DEVICE_FUNCTION(vkCreateFence) \ | ||
48 | VULKAN_DEVICE_FUNCTION(vkCreateImageView) \ | ||
49 | VULKAN_DEVICE_FUNCTION(vkCreateSemaphore) \ | ||
50 | VULKAN_DEVICE_FUNCTION(vkCreateSwapchainKHR) \ | ||
51 | VULKAN_DEVICE_FUNCTION(vkDestroyCommandPool) \ | ||
52 | VULKAN_DEVICE_FUNCTION(vkDestroyDevice) \ | ||
53 | VULKAN_DEVICE_FUNCTION(vkDestroyFence) \ | ||
54 | VULKAN_DEVICE_FUNCTION(vkDestroyImageView) \ | ||
55 | VULKAN_DEVICE_FUNCTION(vkDestroySemaphore) \ | ||
56 | VULKAN_DEVICE_FUNCTION(vkDestroySwapchainKHR) \ | ||
57 | VULKAN_DEVICE_FUNCTION(vkDeviceWaitIdle) \ | ||
58 | VULKAN_DEVICE_FUNCTION(vkEndCommandBuffer) \ | ||
59 | VULKAN_DEVICE_FUNCTION(vkFreeCommandBuffers) \ | ||
60 | VULKAN_DEVICE_FUNCTION(vkGetDeviceQueue) \ | ||
61 | VULKAN_DEVICE_FUNCTION(vkGetFenceStatus) \ | ||
62 | VULKAN_DEVICE_FUNCTION(vkGetSwapchainImagesKHR) \ | ||
63 | VULKAN_DEVICE_FUNCTION(vkQueuePresentKHR) \ | ||
64 | VULKAN_DEVICE_FUNCTION(vkQueueSubmit) \ | ||
65 | VULKAN_DEVICE_FUNCTION(vkResetCommandBuffer) \ | ||
66 | VULKAN_DEVICE_FUNCTION(vkResetFences) \ | ||
67 | VULKAN_DEVICE_FUNCTION(vkWaitForFences) \ | ||
68 | VULKAN_GLOBAL_FUNCTION(vkCreateInstance) \ | ||
69 | VULKAN_GLOBAL_FUNCTION(vkEnumerateInstanceExtensionProperties) \ | ||
70 | VULKAN_GLOBAL_FUNCTION(vkEnumerateInstanceLayerProperties) \ | ||
71 | VULKAN_INSTANCE_FUNCTION(vkCreateDevice) \ | ||
72 | VULKAN_INSTANCE_FUNCTION(vkDestroyInstance) \ | ||
73 | VULKAN_INSTANCE_FUNCTION(vkDestroySurfaceKHR) \ | ||
74 | VULKAN_INSTANCE_FUNCTION(vkEnumerateDeviceExtensionProperties) \ | ||
75 | VULKAN_INSTANCE_FUNCTION(vkEnumeratePhysicalDevices) \ | ||
76 | VULKAN_INSTANCE_FUNCTION(vkGetDeviceProcAddr) \ | ||
77 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceFeatures) \ | ||
78 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties) \ | ||
79 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties) \ | ||
80 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfaceCapabilitiesKHR) \ | ||
81 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfaceFormatsKHR) \ | ||
82 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfacePresentModesKHR) \ | ||
83 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR) | ||
84 | |||
85 | #define VULKAN_DEVICE_FUNCTION(name) static PFN_##name name = NULL; | ||
86 | #define VULKAN_GLOBAL_FUNCTION(name) static PFN_##name name = NULL; | ||
87 | #define VULKAN_INSTANCE_FUNCTION(name) static PFN_##name name = NULL; | ||
88 | VULKAN_FUNCTIONS() | ||
89 | #undef VULKAN_DEVICE_FUNCTION | ||
90 | #undef VULKAN_GLOBAL_FUNCTION | ||
91 | #undef VULKAN_INSTANCE_FUNCTION | ||
92 | static PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; | ||
93 | |||
94 | /* Based on the headers found in | ||
95 | * https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers | ||
96 | */ | ||
97 | #if VK_HEADER_VERSION < 22 | ||
98 | enum | ||
99 | { | ||
100 | VK_ERROR_FRAGMENTED_POOL = -12, | ||
101 | }; | ||
102 | #endif | ||
103 | #if VK_HEADER_VERSION < 38 | ||
104 | enum | ||
105 | { | ||
106 | VK_ERROR_OUT_OF_POOL_MEMORY_KHR = -1000069000 | ||
107 | }; | ||
108 | #endif | ||
109 | |||
110 | static const char *getVulkanResultString(VkResult result) | ||
111 | { | ||
112 | switch ((int)result) { | ||
113 | #define RESULT_CASE(x) \ | ||
114 | case x: \ | ||
115 | return #x | ||
116 | RESULT_CASE(VK_SUCCESS); | ||
117 | RESULT_CASE(VK_NOT_READY); | ||
118 | RESULT_CASE(VK_TIMEOUT); | ||
119 | RESULT_CASE(VK_EVENT_SET); | ||
120 | RESULT_CASE(VK_EVENT_RESET); | ||
121 | RESULT_CASE(VK_INCOMPLETE); | ||
122 | RESULT_CASE(VK_ERROR_OUT_OF_HOST_MEMORY); | ||
123 | RESULT_CASE(VK_ERROR_OUT_OF_DEVICE_MEMORY); | ||
124 | RESULT_CASE(VK_ERROR_INITIALIZATION_FAILED); | ||
125 | RESULT_CASE(VK_ERROR_DEVICE_LOST); | ||
126 | RESULT_CASE(VK_ERROR_MEMORY_MAP_FAILED); | ||
127 | RESULT_CASE(VK_ERROR_LAYER_NOT_PRESENT); | ||
128 | RESULT_CASE(VK_ERROR_EXTENSION_NOT_PRESENT); | ||
129 | RESULT_CASE(VK_ERROR_FEATURE_NOT_PRESENT); | ||
130 | RESULT_CASE(VK_ERROR_INCOMPATIBLE_DRIVER); | ||
131 | RESULT_CASE(VK_ERROR_TOO_MANY_OBJECTS); | ||
132 | RESULT_CASE(VK_ERROR_FORMAT_NOT_SUPPORTED); | ||
133 | RESULT_CASE(VK_ERROR_FRAGMENTED_POOL); | ||
134 | RESULT_CASE(VK_ERROR_SURFACE_LOST_KHR); | ||
135 | RESULT_CASE(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR); | ||
136 | RESULT_CASE(VK_SUBOPTIMAL_KHR); | ||
137 | RESULT_CASE(VK_ERROR_OUT_OF_DATE_KHR); | ||
138 | RESULT_CASE(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR); | ||
139 | RESULT_CASE(VK_ERROR_VALIDATION_FAILED_EXT); | ||
140 | RESULT_CASE(VK_ERROR_OUT_OF_POOL_MEMORY_KHR); | ||
141 | RESULT_CASE(VK_ERROR_INVALID_SHADER_NV); | ||
142 | #undef RESULT_CASE | ||
143 | default: | ||
144 | break; | ||
145 | } | ||
146 | return (result < 0) ? "VK_ERROR_<Unknown>" : "VK_<Unknown>"; | ||
147 | } | ||
148 | |||
149 | typedef struct VulkanContext | ||
150 | { | ||
151 | SDL_Window *window; | ||
152 | VkInstance instance; | ||
153 | VkDevice device; | ||
154 | VkSurfaceKHR surface; | ||
155 | VkSwapchainKHR swapchain; | ||
156 | VkPhysicalDeviceProperties physicalDeviceProperties; | ||
157 | VkPhysicalDeviceFeatures physicalDeviceFeatures; | ||
158 | uint32_t graphicsQueueFamilyIndex; | ||
159 | uint32_t presentQueueFamilyIndex; | ||
160 | VkPhysicalDevice physicalDevice; | ||
161 | VkQueue graphicsQueue; | ||
162 | VkQueue presentQueue; | ||
163 | VkSemaphore imageAvailableSemaphore; | ||
164 | VkSemaphore renderingFinishedSemaphore; | ||
165 | VkSurfaceCapabilitiesKHR surfaceCapabilities; | ||
166 | VkSurfaceFormatKHR *surfaceFormats; | ||
167 | uint32_t surfaceFormatsAllocatedCount; | ||
168 | uint32_t surfaceFormatsCount; | ||
169 | uint32_t swapchainDesiredImageCount; | ||
170 | VkSurfaceFormatKHR surfaceFormat; | ||
171 | VkExtent2D swapchainSize; | ||
172 | VkCommandPool commandPool; | ||
173 | uint32_t swapchainImageCount; | ||
174 | VkImage *swapchainImages; | ||
175 | VkCommandBuffer *commandBuffers; | ||
176 | VkFence *fences; | ||
177 | } VulkanContext; | ||
178 | |||
179 | static SDLTest_CommonState *state; | ||
180 | static VulkanContext *vulkanContexts = NULL; /* an array of state->num_windows items */ | ||
181 | static VulkanContext *vulkanContext = NULL; /* for the currently-rendering window */ | ||
182 | |||
183 | static void shutdownVulkan(bool doDestroySwapchain); | ||
184 | |||
185 | /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ | ||
186 | static void quit(int rc) | ||
187 | { | ||
188 | shutdownVulkan(true); | ||
189 | SDLTest_CommonQuit(state); | ||
190 | /* Let 'main()' return normally */ | ||
191 | if (rc != 0) { | ||
192 | exit(rc); | ||
193 | } | ||
194 | } | ||
195 | |||
196 | static void loadGlobalFunctions(void) | ||
197 | { | ||
198 | vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr(); | ||
199 | if (!vkGetInstanceProcAddr) { | ||
200 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
201 | "SDL_Vulkan_GetVkGetInstanceProcAddr(): %s", | ||
202 | SDL_GetError()); | ||
203 | quit(2); | ||
204 | } | ||
205 | |||
206 | #define VULKAN_DEVICE_FUNCTION(name) | ||
207 | #define VULKAN_GLOBAL_FUNCTION(name) \ | ||
208 | name = (PFN_##name)vkGetInstanceProcAddr(VK_NULL_HANDLE, #name); \ | ||
209 | if (!name) { \ | ||
210 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, \ | ||
211 | "vkGetInstanceProcAddr(VK_NULL_HANDLE, \"" #name "\") failed"); \ | ||
212 | quit(2); \ | ||
213 | } | ||
214 | #define VULKAN_INSTANCE_FUNCTION(name) | ||
215 | VULKAN_FUNCTIONS() | ||
216 | #undef VULKAN_DEVICE_FUNCTION | ||
217 | #undef VULKAN_GLOBAL_FUNCTION | ||
218 | #undef VULKAN_INSTANCE_FUNCTION | ||
219 | } | ||
220 | |||
221 | static void createInstance(void) | ||
222 | { | ||
223 | VkApplicationInfo appInfo = { 0 }; | ||
224 | VkInstanceCreateInfo instanceCreateInfo = { 0 }; | ||
225 | VkResult result; | ||
226 | |||
227 | appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | ||
228 | appInfo.apiVersion = VK_API_VERSION_1_0; | ||
229 | instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | ||
230 | instanceCreateInfo.pApplicationInfo = &appInfo; | ||
231 | #ifdef __APPLE__ | ||
232 | instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; | ||
233 | #endif | ||
234 | |||
235 | instanceCreateInfo.ppEnabledExtensionNames = SDL_Vulkan_GetInstanceExtensions(&instanceCreateInfo.enabledExtensionCount); | ||
236 | result = vkCreateInstance(&instanceCreateInfo, NULL, &vulkanContext->instance); | ||
237 | if (result != VK_SUCCESS) { | ||
238 | vulkanContext->instance = VK_NULL_HANDLE; | ||
239 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
240 | "vkCreateInstance(): %s", | ||
241 | getVulkanResultString(result)); | ||
242 | quit(2); | ||
243 | } | ||
244 | } | ||
245 | |||
246 | static void loadInstanceFunctions(void) | ||
247 | { | ||
248 | #define VULKAN_DEVICE_FUNCTION(name) | ||
249 | #define VULKAN_GLOBAL_FUNCTION(name) | ||
250 | #define VULKAN_INSTANCE_FUNCTION(name) \ | ||
251 | name = (PFN_##name)vkGetInstanceProcAddr(vulkanContext->instance, #name); \ | ||
252 | if (!name) { \ | ||
253 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, \ | ||
254 | "vkGetInstanceProcAddr(instance, \"" #name "\") failed"); \ | ||
255 | quit(2); \ | ||
256 | } | ||
257 | VULKAN_FUNCTIONS() | ||
258 | #undef VULKAN_DEVICE_FUNCTION | ||
259 | #undef VULKAN_GLOBAL_FUNCTION | ||
260 | #undef VULKAN_INSTANCE_FUNCTION | ||
261 | } | ||
262 | |||
263 | static void createSurface(void) | ||
264 | { | ||
265 | if (!SDL_Vulkan_CreateSurface(vulkanContext->window, vulkanContext->instance, NULL, &vulkanContext->surface)) { | ||
266 | vulkanContext->surface = VK_NULL_HANDLE; | ||
267 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Vulkan_CreateSurface(): %s", SDL_GetError()); | ||
268 | quit(2); | ||
269 | } | ||
270 | } | ||
271 | |||
272 | static void findPhysicalDevice(void) | ||
273 | { | ||
274 | uint32_t physicalDeviceCount = 0; | ||
275 | VkPhysicalDevice *physicalDevices; | ||
276 | VkQueueFamilyProperties *queueFamiliesProperties = NULL; | ||
277 | uint32_t queueFamiliesPropertiesAllocatedSize = 0; | ||
278 | VkExtensionProperties *deviceExtensions = NULL; | ||
279 | uint32_t deviceExtensionsAllocatedSize = 0; | ||
280 | uint32_t physicalDeviceIndex; | ||
281 | VkResult result; | ||
282 | |||
283 | result = vkEnumeratePhysicalDevices(vulkanContext->instance, &physicalDeviceCount, NULL); | ||
284 | if (result != VK_SUCCESS) { | ||
285 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
286 | "vkEnumeratePhysicalDevices(): %s", | ||
287 | getVulkanResultString(result)); | ||
288 | quit(2); | ||
289 | } | ||
290 | if (physicalDeviceCount == 0) { | ||
291 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
292 | "vkEnumeratePhysicalDevices(): no physical devices"); | ||
293 | quit(2); | ||
294 | } | ||
295 | physicalDevices = (VkPhysicalDevice *)SDL_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount); | ||
296 | if (!physicalDevices) { | ||
297 | quit(2); | ||
298 | } | ||
299 | result = vkEnumeratePhysicalDevices(vulkanContext->instance, &physicalDeviceCount, physicalDevices); | ||
300 | if (result != VK_SUCCESS) { | ||
301 | SDL_free(physicalDevices); | ||
302 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
303 | "vkEnumeratePhysicalDevices(): %s", | ||
304 | getVulkanResultString(result)); | ||
305 | quit(2); | ||
306 | } | ||
307 | vulkanContext->physicalDevice = NULL; | ||
308 | for (physicalDeviceIndex = 0; physicalDeviceIndex < physicalDeviceCount; physicalDeviceIndex++) { | ||
309 | uint32_t queueFamiliesCount = 0; | ||
310 | uint32_t queueFamilyIndex; | ||
311 | uint32_t deviceExtensionCount = 0; | ||
312 | bool hasSwapchainExtension = false; | ||
313 | bool supportsPresent; | ||
314 | uint32_t i; | ||
315 | |||
316 | VkPhysicalDevice physicalDevice = physicalDevices[physicalDeviceIndex]; | ||
317 | vkGetPhysicalDeviceProperties(physicalDevice, &vulkanContext->physicalDeviceProperties); | ||
318 | if (VK_VERSION_MAJOR(vulkanContext->physicalDeviceProperties.apiVersion) < 1) { | ||
319 | continue; | ||
320 | } | ||
321 | vkGetPhysicalDeviceFeatures(physicalDevice, &vulkanContext->physicalDeviceFeatures); | ||
322 | vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamiliesCount, NULL); | ||
323 | if (queueFamiliesCount == 0) { | ||
324 | continue; | ||
325 | } | ||
326 | if (queueFamiliesPropertiesAllocatedSize < queueFamiliesCount) { | ||
327 | SDL_free(queueFamiliesProperties); | ||
328 | queueFamiliesPropertiesAllocatedSize = queueFamiliesCount; | ||
329 | queueFamiliesProperties = (VkQueueFamilyProperties *)SDL_malloc(sizeof(VkQueueFamilyProperties) * queueFamiliesPropertiesAllocatedSize); | ||
330 | if (!queueFamiliesProperties) { | ||
331 | SDL_free(physicalDevices); | ||
332 | SDL_free(deviceExtensions); | ||
333 | quit(2); | ||
334 | } | ||
335 | } | ||
336 | vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamiliesCount, queueFamiliesProperties); | ||
337 | vulkanContext->graphicsQueueFamilyIndex = queueFamiliesCount; | ||
338 | vulkanContext->presentQueueFamilyIndex = queueFamiliesCount; | ||
339 | for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; queueFamilyIndex++) { | ||
340 | VkBool32 supported = 0; | ||
341 | |||
342 | if (queueFamiliesProperties[queueFamilyIndex].queueCount == 0) { | ||
343 | continue; | ||
344 | } | ||
345 | |||
346 | if (queueFamiliesProperties[queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | ||
347 | vulkanContext->graphicsQueueFamilyIndex = queueFamilyIndex; | ||
348 | } | ||
349 | |||
350 | result = vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, queueFamilyIndex, vulkanContext->surface, &supported); | ||
351 | if (result != VK_SUCCESS) { | ||
352 | SDL_free(physicalDevices); | ||
353 | SDL_free(queueFamiliesProperties); | ||
354 | SDL_free(deviceExtensions); | ||
355 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
356 | "vkGetPhysicalDeviceSurfaceSupportKHR(): %s", | ||
357 | getVulkanResultString(result)); | ||
358 | quit(2); | ||
359 | } | ||
360 | if (supported) { | ||
361 | /* This check isn't necessary if you are able to check a | ||
362 | * VkSurfaceKHR like above, but just as a sanity check we do | ||
363 | * this here as part of testing the API. | ||
364 | * -flibit | ||
365 | */ | ||
366 | supportsPresent = SDL_Vulkan_GetPresentationSupport(vulkanContext->instance, physicalDevice, queueFamilyIndex); | ||
367 | if (!supportsPresent) { | ||
368 | SDL_free(physicalDevices); | ||
369 | SDL_free(queueFamiliesProperties); | ||
370 | SDL_free(deviceExtensions); | ||
371 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
372 | "SDL_Vulkan_GetPresentationSupport(): %s", | ||
373 | SDL_GetError()); | ||
374 | quit(2); | ||
375 | } | ||
376 | |||
377 | vulkanContext->presentQueueFamilyIndex = queueFamilyIndex; | ||
378 | if (queueFamiliesProperties[queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | ||
379 | break; // use this queue because it can present and do graphics | ||
380 | } | ||
381 | } | ||
382 | } | ||
383 | |||
384 | if (vulkanContext->graphicsQueueFamilyIndex == queueFamiliesCount) { // no good queues found | ||
385 | continue; | ||
386 | } | ||
387 | if (vulkanContext->presentQueueFamilyIndex == queueFamiliesCount) { // no good queues found | ||
388 | continue; | ||
389 | } | ||
390 | result = vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &deviceExtensionCount, NULL); | ||
391 | if (result != VK_SUCCESS) { | ||
392 | SDL_free(physicalDevices); | ||
393 | SDL_free(queueFamiliesProperties); | ||
394 | SDL_free(deviceExtensions); | ||
395 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
396 | "vkEnumerateDeviceExtensionProperties(): %s", | ||
397 | getVulkanResultString(result)); | ||
398 | quit(2); | ||
399 | } | ||
400 | if (deviceExtensionCount == 0) { | ||
401 | continue; | ||
402 | } | ||
403 | if (deviceExtensionsAllocatedSize < deviceExtensionCount) { | ||
404 | SDL_free(deviceExtensions); | ||
405 | deviceExtensionsAllocatedSize = deviceExtensionCount; | ||
406 | deviceExtensions = SDL_malloc(sizeof(VkExtensionProperties) * deviceExtensionsAllocatedSize); | ||
407 | if (!deviceExtensions) { | ||
408 | SDL_free(physicalDevices); | ||
409 | SDL_free(queueFamiliesProperties); | ||
410 | quit(2); | ||
411 | } | ||
412 | } | ||
413 | result = vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &deviceExtensionCount, deviceExtensions); | ||
414 | if (result != VK_SUCCESS) { | ||
415 | SDL_free(physicalDevices); | ||
416 | SDL_free(queueFamiliesProperties); | ||
417 | SDL_free(deviceExtensions); | ||
418 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
419 | "vkEnumerateDeviceExtensionProperties(): %s", | ||
420 | getVulkanResultString(result)); | ||
421 | quit(2); | ||
422 | } | ||
423 | for (i = 0; i < deviceExtensionCount; i++) { | ||
424 | if (SDL_strcmp(deviceExtensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { | ||
425 | hasSwapchainExtension = true; | ||
426 | break; | ||
427 | } | ||
428 | } | ||
429 | if (!hasSwapchainExtension) { | ||
430 | continue; | ||
431 | } | ||
432 | vulkanContext->physicalDevice = physicalDevice; | ||
433 | break; | ||
434 | } | ||
435 | SDL_free(physicalDevices); | ||
436 | SDL_free(queueFamiliesProperties); | ||
437 | SDL_free(deviceExtensions); | ||
438 | if (!vulkanContext->physicalDevice) { | ||
439 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Vulkan: no viable physical devices found"); | ||
440 | quit(2); | ||
441 | } | ||
442 | } | ||
443 | |||
444 | static void createDevice(void) | ||
445 | { | ||
446 | VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = { { 0 }, { 0 } }; | ||
447 | static const float queuePriority[] = { 1.0f }; | ||
448 | VkDeviceCreateInfo deviceCreateInfo = { 0 }; | ||
449 | static const char *const deviceExtensionNames[] = { | ||
450 | VK_KHR_SWAPCHAIN_EXTENSION_NAME, | ||
451 | #ifdef __APPLE__ | ||
452 | "VK_KHR_portability_subset" | ||
453 | #endif | ||
454 | }; | ||
455 | VkResult result; | ||
456 | |||
457 | deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | ||
458 | deviceCreateInfo.queueCreateInfoCount = 0; | ||
459 | deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo; | ||
460 | deviceCreateInfo.pEnabledFeatures = NULL; | ||
461 | deviceCreateInfo.enabledExtensionCount = SDL_arraysize(deviceExtensionNames); | ||
462 | deviceCreateInfo.ppEnabledExtensionNames = deviceExtensionNames; | ||
463 | |||
464 | deviceQueueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | ||
465 | deviceQueueCreateInfo[0].queueFamilyIndex = vulkanContext->graphicsQueueFamilyIndex; | ||
466 | deviceQueueCreateInfo[0].queueCount = 1; | ||
467 | deviceQueueCreateInfo[0].pQueuePriorities = queuePriority; | ||
468 | ++deviceCreateInfo.queueCreateInfoCount; | ||
469 | |||
470 | if (vulkanContext->presentQueueFamilyIndex != vulkanContext->graphicsQueueFamilyIndex) { | ||
471 | deviceQueueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | ||
472 | deviceQueueCreateInfo[1].queueFamilyIndex = vulkanContext->presentQueueFamilyIndex; | ||
473 | deviceQueueCreateInfo[1].queueCount = 1; | ||
474 | deviceQueueCreateInfo[1].pQueuePriorities = queuePriority; | ||
475 | ++deviceCreateInfo.queueCreateInfoCount; | ||
476 | } | ||
477 | |||
478 | result = vkCreateDevice(vulkanContext->physicalDevice, &deviceCreateInfo, NULL, &vulkanContext->device); | ||
479 | if (result != VK_SUCCESS) { | ||
480 | vulkanContext->device = VK_NULL_HANDLE; | ||
481 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "vkCreateDevice(): %s", getVulkanResultString(result)); | ||
482 | quit(2); | ||
483 | } | ||
484 | } | ||
485 | |||
486 | static void loadDeviceFunctions(void) | ||
487 | { | ||
488 | #define VULKAN_DEVICE_FUNCTION(name) \ | ||
489 | name = (PFN_##name)vkGetDeviceProcAddr(vulkanContext->device, #name); \ | ||
490 | if (!name) { \ | ||
491 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, \ | ||
492 | "vkGetDeviceProcAddr(device, \"" #name "\") failed"); \ | ||
493 | quit(2); \ | ||
494 | } | ||
495 | #define VULKAN_GLOBAL_FUNCTION(name) | ||
496 | #define VULKAN_INSTANCE_FUNCTION(name) | ||
497 | VULKAN_FUNCTIONS() | ||
498 | #undef VULKAN_DEVICE_FUNCTION | ||
499 | #undef VULKAN_GLOBAL_FUNCTION | ||
500 | #undef VULKAN_INSTANCE_FUNCTION | ||
501 | } | ||
502 | |||
503 | #undef VULKAN_FUNCTIONS | ||
504 | |||
505 | static void getQueues(void) | ||
506 | { | ||
507 | vkGetDeviceQueue(vulkanContext->device, | ||
508 | vulkanContext->graphicsQueueFamilyIndex, | ||
509 | 0, | ||
510 | &vulkanContext->graphicsQueue); | ||
511 | if (vulkanContext->graphicsQueueFamilyIndex != vulkanContext->presentQueueFamilyIndex) { | ||
512 | vkGetDeviceQueue(vulkanContext->device, | ||
513 | vulkanContext->presentQueueFamilyIndex, | ||
514 | 0, | ||
515 | &vulkanContext->presentQueue); | ||
516 | } else { | ||
517 | vulkanContext->presentQueue = vulkanContext->graphicsQueue; | ||
518 | } | ||
519 | } | ||
520 | |||
521 | static void createSemaphore(VkSemaphore *semaphore) | ||
522 | { | ||
523 | VkResult result; | ||
524 | |||
525 | VkSemaphoreCreateInfo createInfo = { 0 }; | ||
526 | createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; | ||
527 | result = vkCreateSemaphore(vulkanContext->device, &createInfo, NULL, semaphore); | ||
528 | if (result != VK_SUCCESS) { | ||
529 | *semaphore = VK_NULL_HANDLE; | ||
530 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
531 | "vkCreateSemaphore(): %s", | ||
532 | getVulkanResultString(result)); | ||
533 | quit(2); | ||
534 | } | ||
535 | } | ||
536 | |||
537 | static void createSemaphores(void) | ||
538 | { | ||
539 | createSemaphore(&vulkanContext->imageAvailableSemaphore); | ||
540 | createSemaphore(&vulkanContext->renderingFinishedSemaphore); | ||
541 | } | ||
542 | |||
543 | static void getSurfaceCaps(void) | ||
544 | { | ||
545 | VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vulkanContext->physicalDevice, vulkanContext->surface, &vulkanContext->surfaceCapabilities); | ||
546 | if (result != VK_SUCCESS) { | ||
547 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
548 | "vkGetPhysicalDeviceSurfaceCapabilitiesKHR(): %s", | ||
549 | getVulkanResultString(result)); | ||
550 | quit(2); | ||
551 | } | ||
552 | |||
553 | // check surface usage | ||
554 | if (!(vulkanContext->surfaceCapabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) { | ||
555 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
556 | "Vulkan surface doesn't support VK_IMAGE_USAGE_TRANSFER_DST_BIT"); | ||
557 | quit(2); | ||
558 | } | ||
559 | } | ||
560 | |||
561 | static void getSurfaceFormats(void) | ||
562 | { | ||
563 | VkResult result = vkGetPhysicalDeviceSurfaceFormatsKHR(vulkanContext->physicalDevice, | ||
564 | vulkanContext->surface, | ||
565 | &vulkanContext->surfaceFormatsCount, | ||
566 | NULL); | ||
567 | if (result != VK_SUCCESS) { | ||
568 | vulkanContext->surfaceFormatsCount = 0; | ||
569 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
570 | "vkGetPhysicalDeviceSurfaceFormatsKHR(): %s", | ||
571 | getVulkanResultString(result)); | ||
572 | quit(2); | ||
573 | } | ||
574 | if (vulkanContext->surfaceFormatsCount > vulkanContext->surfaceFormatsAllocatedCount) { | ||
575 | vulkanContext->surfaceFormatsAllocatedCount = vulkanContext->surfaceFormatsCount; | ||
576 | SDL_free(vulkanContext->surfaceFormats); | ||
577 | vulkanContext->surfaceFormats = (VkSurfaceFormatKHR *)SDL_malloc(sizeof(VkSurfaceFormatKHR) * vulkanContext->surfaceFormatsAllocatedCount); | ||
578 | if (!vulkanContext->surfaceFormats) { | ||
579 | vulkanContext->surfaceFormatsCount = 0; | ||
580 | quit(2); | ||
581 | } | ||
582 | } | ||
583 | result = vkGetPhysicalDeviceSurfaceFormatsKHR(vulkanContext->physicalDevice, | ||
584 | vulkanContext->surface, | ||
585 | &vulkanContext->surfaceFormatsCount, | ||
586 | vulkanContext->surfaceFormats); | ||
587 | if (result != VK_SUCCESS) { | ||
588 | vulkanContext->surfaceFormatsCount = 0; | ||
589 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
590 | "vkGetPhysicalDeviceSurfaceFormatsKHR(): %s", | ||
591 | getVulkanResultString(result)); | ||
592 | quit(2); | ||
593 | } | ||
594 | } | ||
595 | |||
596 | static void getSwapchainImages(void) | ||
597 | { | ||
598 | VkResult result; | ||
599 | |||
600 | SDL_free(vulkanContext->swapchainImages); | ||
601 | vulkanContext->swapchainImages = NULL; | ||
602 | result = vkGetSwapchainImagesKHR(vulkanContext->device, vulkanContext->swapchain, &vulkanContext->swapchainImageCount, NULL); | ||
603 | if (result != VK_SUCCESS) { | ||
604 | vulkanContext->swapchainImageCount = 0; | ||
605 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
606 | "vkGetSwapchainImagesKHR(): %s", | ||
607 | getVulkanResultString(result)); | ||
608 | quit(2); | ||
609 | } | ||
610 | vulkanContext->swapchainImages = SDL_malloc(sizeof(VkImage) * vulkanContext->swapchainImageCount); | ||
611 | if (!vulkanContext->swapchainImages) { | ||
612 | quit(2); | ||
613 | } | ||
614 | result = vkGetSwapchainImagesKHR(vulkanContext->device, | ||
615 | vulkanContext->swapchain, | ||
616 | &vulkanContext->swapchainImageCount, | ||
617 | vulkanContext->swapchainImages); | ||
618 | if (result != VK_SUCCESS) { | ||
619 | SDL_free(vulkanContext->swapchainImages); | ||
620 | vulkanContext->swapchainImages = NULL; | ||
621 | vulkanContext->swapchainImageCount = 0; | ||
622 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
623 | "vkGetSwapchainImagesKHR(): %s", | ||
624 | getVulkanResultString(result)); | ||
625 | quit(2); | ||
626 | } | ||
627 | } | ||
628 | |||
629 | static bool createSwapchain(void) | ||
630 | { | ||
631 | uint32_t i; | ||
632 | int w, h; | ||
633 | VkSwapchainCreateInfoKHR createInfo = { 0 }; | ||
634 | VkResult result; | ||
635 | SDL_WindowFlags flags; | ||
636 | |||
637 | // pick an image count | ||
638 | vulkanContext->swapchainDesiredImageCount = vulkanContext->surfaceCapabilities.minImageCount + 1; | ||
639 | if ((vulkanContext->swapchainDesiredImageCount > vulkanContext->surfaceCapabilities.maxImageCount) && | ||
640 | (vulkanContext->surfaceCapabilities.maxImageCount > 0)) { | ||
641 | vulkanContext->swapchainDesiredImageCount = vulkanContext->surfaceCapabilities.maxImageCount; | ||
642 | } | ||
643 | |||
644 | // pick a format | ||
645 | if ((vulkanContext->surfaceFormatsCount == 1) && | ||
646 | (vulkanContext->surfaceFormats[0].format == VK_FORMAT_UNDEFINED)) { | ||
647 | // aren't any preferred formats, so we pick | ||
648 | vulkanContext->surfaceFormat.colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; | ||
649 | vulkanContext->surfaceFormat.format = VK_FORMAT_R8G8B8A8_UNORM; | ||
650 | } else { | ||
651 | vulkanContext->surfaceFormat = vulkanContext->surfaceFormats[0]; | ||
652 | for (i = 0; i < vulkanContext->surfaceFormatsCount; i++) { | ||
653 | if (vulkanContext->surfaceFormats[i].format == VK_FORMAT_R8G8B8A8_UNORM) { | ||
654 | vulkanContext->surfaceFormat = vulkanContext->surfaceFormats[i]; | ||
655 | break; | ||
656 | } | ||
657 | } | ||
658 | } | ||
659 | |||
660 | // get size | ||
661 | SDL_GetWindowSizeInPixels(vulkanContext->window, &w, &h); | ||
662 | |||
663 | // get flags | ||
664 | flags = SDL_GetWindowFlags(vulkanContext->window); | ||
665 | |||
666 | // Clamp the size to the allowable image extent. | ||
667 | // SDL_GetWindowSizeInPixels()'s result it not always in this range (bug #3287) | ||
668 | vulkanContext->swapchainSize.width = SDL_clamp((uint32_t)w, | ||
669 | vulkanContext->surfaceCapabilities.minImageExtent.width, | ||
670 | vulkanContext->surfaceCapabilities.maxImageExtent.width); | ||
671 | |||
672 | vulkanContext->swapchainSize.height = SDL_clamp((uint32_t)h, | ||
673 | vulkanContext->surfaceCapabilities.minImageExtent.height, | ||
674 | vulkanContext->surfaceCapabilities.maxImageExtent.height); | ||
675 | |||
676 | if (w == 0 || h == 0) { | ||
677 | return false; | ||
678 | } | ||
679 | |||
680 | getSurfaceCaps(); | ||
681 | |||
682 | createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; | ||
683 | createInfo.surface = vulkanContext->surface; | ||
684 | createInfo.minImageCount = vulkanContext->swapchainDesiredImageCount; | ||
685 | createInfo.imageFormat = vulkanContext->surfaceFormat.format; | ||
686 | createInfo.imageColorSpace = vulkanContext->surfaceFormat.colorSpace; | ||
687 | createInfo.imageExtent = vulkanContext->swapchainSize; | ||
688 | createInfo.imageArrayLayers = 1; | ||
689 | createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; | ||
690 | createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; | ||
691 | createInfo.preTransform = vulkanContext->surfaceCapabilities.currentTransform; | ||
692 | if (flags & SDL_WINDOW_TRANSPARENT) { | ||
693 | createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; | ||
694 | } else { | ||
695 | createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; | ||
696 | } | ||
697 | createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; | ||
698 | createInfo.clipped = VK_TRUE; | ||
699 | createInfo.oldSwapchain = vulkanContext->swapchain; | ||
700 | result = vkCreateSwapchainKHR(vulkanContext->device, &createInfo, NULL, &vulkanContext->swapchain); | ||
701 | |||
702 | if (createInfo.oldSwapchain) { | ||
703 | vkDestroySwapchainKHR(vulkanContext->device, createInfo.oldSwapchain, NULL); | ||
704 | } | ||
705 | |||
706 | if (result != VK_SUCCESS) { | ||
707 | vulkanContext->swapchain = VK_NULL_HANDLE; | ||
708 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
709 | "vkCreateSwapchainKHR(): %s", | ||
710 | getVulkanResultString(result)); | ||
711 | quit(2); | ||
712 | } | ||
713 | |||
714 | getSwapchainImages(); | ||
715 | return true; | ||
716 | } | ||
717 | |||
718 | static void destroySwapchain(void) | ||
719 | { | ||
720 | if (vulkanContext->swapchain) { | ||
721 | vkDestroySwapchainKHR(vulkanContext->device, vulkanContext->swapchain, NULL); | ||
722 | vulkanContext->swapchain = VK_NULL_HANDLE; | ||
723 | } | ||
724 | SDL_free(vulkanContext->swapchainImages); | ||
725 | vulkanContext->swapchainImages = NULL; | ||
726 | } | ||
727 | |||
728 | static void destroyCommandBuffers(void) | ||
729 | { | ||
730 | if (vulkanContext->commandBuffers) { | ||
731 | vkFreeCommandBuffers(vulkanContext->device, | ||
732 | vulkanContext->commandPool, | ||
733 | vulkanContext->swapchainImageCount, | ||
734 | vulkanContext->commandBuffers); | ||
735 | SDL_free(vulkanContext->commandBuffers); | ||
736 | vulkanContext->commandBuffers = NULL; | ||
737 | } | ||
738 | } | ||
739 | |||
740 | static void destroyCommandPool(void) | ||
741 | { | ||
742 | if (vulkanContext->commandPool) { | ||
743 | vkDestroyCommandPool(vulkanContext->device, vulkanContext->commandPool, NULL); | ||
744 | } | ||
745 | vulkanContext->commandPool = VK_NULL_HANDLE; | ||
746 | } | ||
747 | |||
748 | static void createCommandPool(void) | ||
749 | { | ||
750 | VkResult result; | ||
751 | VkCommandPoolCreateInfo createInfo = { 0 }; | ||
752 | createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; | ||
753 | createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; | ||
754 | createInfo.queueFamilyIndex = vulkanContext->graphicsQueueFamilyIndex; | ||
755 | result = vkCreateCommandPool(vulkanContext->device, &createInfo, NULL, &vulkanContext->commandPool); | ||
756 | if (result != VK_SUCCESS) { | ||
757 | vulkanContext->commandPool = VK_NULL_HANDLE; | ||
758 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
759 | "vkCreateCommandPool(): %s", | ||
760 | getVulkanResultString(result)); | ||
761 | quit(2); | ||
762 | } | ||
763 | } | ||
764 | |||
765 | static void createCommandBuffers(void) | ||
766 | { | ||
767 | VkResult result; | ||
768 | VkCommandBufferAllocateInfo allocateInfo = { 0 }; | ||
769 | allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | ||
770 | allocateInfo.commandPool = vulkanContext->commandPool; | ||
771 | allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | ||
772 | allocateInfo.commandBufferCount = vulkanContext->swapchainImageCount; | ||
773 | vulkanContext->commandBuffers = (VkCommandBuffer *)SDL_malloc(sizeof(VkCommandBuffer) * vulkanContext->swapchainImageCount); | ||
774 | result = vkAllocateCommandBuffers(vulkanContext->device, &allocateInfo, vulkanContext->commandBuffers); | ||
775 | if (result != VK_SUCCESS) { | ||
776 | SDL_free(vulkanContext->commandBuffers); | ||
777 | vulkanContext->commandBuffers = NULL; | ||
778 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
779 | "vkAllocateCommandBuffers(): %s", | ||
780 | getVulkanResultString(result)); | ||
781 | quit(2); | ||
782 | } | ||
783 | } | ||
784 | |||
785 | static void createFences(void) | ||
786 | { | ||
787 | uint32_t i; | ||
788 | |||
789 | vulkanContext->fences = SDL_malloc(sizeof(VkFence) * vulkanContext->swapchainImageCount); | ||
790 | if (!vulkanContext->fences) { | ||
791 | quit(2); | ||
792 | } | ||
793 | for (i = 0; i < vulkanContext->swapchainImageCount; i++) { | ||
794 | VkResult result; | ||
795 | VkFenceCreateInfo createInfo = { 0 }; | ||
796 | createInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; | ||
797 | createInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; | ||
798 | result = vkCreateFence(vulkanContext->device, &createInfo, NULL, &vulkanContext->fences[i]); | ||
799 | if (result != VK_SUCCESS) { | ||
800 | for (; i > 0; i--) { | ||
801 | vkDestroyFence(vulkanContext->device, vulkanContext->fences[i - 1], NULL); | ||
802 | } | ||
803 | SDL_free(vulkanContext->fences); | ||
804 | vulkanContext->fences = NULL; | ||
805 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
806 | "vkCreateFence(): %s", | ||
807 | getVulkanResultString(result)); | ||
808 | quit(2); | ||
809 | } | ||
810 | } | ||
811 | } | ||
812 | |||
813 | static void destroyFences(void) | ||
814 | { | ||
815 | uint32_t i; | ||
816 | |||
817 | if (!vulkanContext->fences) { | ||
818 | return; | ||
819 | } | ||
820 | |||
821 | for (i = 0; i < vulkanContext->swapchainImageCount; i++) { | ||
822 | vkDestroyFence(vulkanContext->device, vulkanContext->fences[i], NULL); | ||
823 | } | ||
824 | SDL_free(vulkanContext->fences); | ||
825 | vulkanContext->fences = NULL; | ||
826 | } | ||
827 | |||
828 | static void recordPipelineImageBarrier(VkCommandBuffer commandBuffer, | ||
829 | VkAccessFlags sourceAccessMask, | ||
830 | VkAccessFlags destAccessMask, | ||
831 | VkImageLayout sourceLayout, | ||
832 | VkImageLayout destLayout, | ||
833 | VkImage image) | ||
834 | { | ||
835 | VkImageMemoryBarrier barrier = { 0 }; | ||
836 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; | ||
837 | barrier.srcAccessMask = sourceAccessMask; | ||
838 | barrier.dstAccessMask = destAccessMask; | ||
839 | barrier.oldLayout = sourceLayout; | ||
840 | barrier.newLayout = destLayout; | ||
841 | barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | ||
842 | barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | ||
843 | barrier.image = image; | ||
844 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | ||
845 | barrier.subresourceRange.baseMipLevel = 0; | ||
846 | barrier.subresourceRange.levelCount = 1; | ||
847 | barrier.subresourceRange.baseArrayLayer = 0; | ||
848 | barrier.subresourceRange.layerCount = 1; | ||
849 | vkCmdPipelineBarrier(commandBuffer, | ||
850 | VK_PIPELINE_STAGE_TRANSFER_BIT, | ||
851 | VK_PIPELINE_STAGE_TRANSFER_BIT, | ||
852 | 0, | ||
853 | 0, | ||
854 | NULL, | ||
855 | 0, | ||
856 | NULL, | ||
857 | 1, | ||
858 | &barrier); | ||
859 | } | ||
860 | |||
861 | static void rerecordCommandBuffer(uint32_t frameIndex, const VkClearColorValue *clearColor) | ||
862 | { | ||
863 | VkCommandBuffer commandBuffer = vulkanContext->commandBuffers[frameIndex]; | ||
864 | VkImage image = vulkanContext->swapchainImages[frameIndex]; | ||
865 | VkCommandBufferBeginInfo beginInfo = { 0 }; | ||
866 | VkImageSubresourceRange clearRange = { 0 }; | ||
867 | |||
868 | VkResult result = vkResetCommandBuffer(commandBuffer, 0); | ||
869 | if (result != VK_SUCCESS) { | ||
870 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
871 | "vkResetCommandBuffer(): %s", | ||
872 | getVulkanResultString(result)); | ||
873 | quit(2); | ||
874 | } | ||
875 | beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | ||
876 | beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; | ||
877 | result = vkBeginCommandBuffer(commandBuffer, &beginInfo); | ||
878 | if (result != VK_SUCCESS) { | ||
879 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
880 | "vkBeginCommandBuffer(): %s", | ||
881 | getVulkanResultString(result)); | ||
882 | quit(2); | ||
883 | } | ||
884 | recordPipelineImageBarrier(commandBuffer, | ||
885 | 0, | ||
886 | VK_ACCESS_TRANSFER_WRITE_BIT, | ||
887 | VK_IMAGE_LAYOUT_UNDEFINED, | ||
888 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | ||
889 | image); | ||
890 | clearRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | ||
891 | clearRange.baseMipLevel = 0; | ||
892 | clearRange.levelCount = 1; | ||
893 | clearRange.baseArrayLayer = 0; | ||
894 | clearRange.layerCount = 1; | ||
895 | vkCmdClearColorImage(commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, clearColor, 1, &clearRange); | ||
896 | recordPipelineImageBarrier(commandBuffer, | ||
897 | VK_ACCESS_TRANSFER_WRITE_BIT, | ||
898 | VK_ACCESS_MEMORY_READ_BIT, | ||
899 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | ||
900 | VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, | ||
901 | image); | ||
902 | result = vkEndCommandBuffer(commandBuffer); | ||
903 | if (result != VK_SUCCESS) { | ||
904 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
905 | "vkEndCommandBuffer(): %s", | ||
906 | getVulkanResultString(result)); | ||
907 | quit(2); | ||
908 | } | ||
909 | } | ||
910 | |||
911 | static void destroySwapchainAndSwapchainSpecificStuff(bool doDestroySwapchain) | ||
912 | { | ||
913 | if (vkDeviceWaitIdle != NULL) { | ||
914 | vkDeviceWaitIdle(vulkanContext->device); | ||
915 | } | ||
916 | destroyFences(); | ||
917 | destroyCommandBuffers(); | ||
918 | destroyCommandPool(); | ||
919 | if (doDestroySwapchain) { | ||
920 | destroySwapchain(); | ||
921 | } | ||
922 | } | ||
923 | |||
924 | static bool createNewSwapchainAndSwapchainSpecificStuff(void) | ||
925 | { | ||
926 | destroySwapchainAndSwapchainSpecificStuff(false); | ||
927 | getSurfaceCaps(); | ||
928 | getSurfaceFormats(); | ||
929 | if (!createSwapchain()) { | ||
930 | return false; | ||
931 | } | ||
932 | createCommandPool(); | ||
933 | createCommandBuffers(); | ||
934 | createFences(); | ||
935 | return true; | ||
936 | } | ||
937 | |||
938 | static void initVulkan(void) | ||
939 | { | ||
940 | int i; | ||
941 | |||
942 | SDL_Vulkan_LoadLibrary(NULL); | ||
943 | |||
944 | vulkanContexts = (VulkanContext *)SDL_calloc(state->num_windows, sizeof(VulkanContext)); | ||
945 | if (!vulkanContexts) { | ||
946 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!"); | ||
947 | quit(2); | ||
948 | } | ||
949 | |||
950 | for (i = 0; i < state->num_windows; ++i) { | ||
951 | vulkanContext = &vulkanContexts[i]; | ||
952 | vulkanContext->window = state->windows[i]; | ||
953 | loadGlobalFunctions(); | ||
954 | createInstance(); | ||
955 | loadInstanceFunctions(); | ||
956 | createSurface(); | ||
957 | findPhysicalDevice(); | ||
958 | createDevice(); | ||
959 | loadDeviceFunctions(); | ||
960 | getQueues(); | ||
961 | createSemaphores(); | ||
962 | createNewSwapchainAndSwapchainSpecificStuff(); | ||
963 | } | ||
964 | } | ||
965 | |||
966 | static void shutdownVulkan(bool doDestroySwapchain) | ||
967 | { | ||
968 | if (vulkanContexts) { | ||
969 | int i; | ||
970 | for (i = 0; i < state->num_windows; ++i) { | ||
971 | vulkanContext = &vulkanContexts[i]; | ||
972 | if (vulkanContext->device && vkDeviceWaitIdle) { | ||
973 | vkDeviceWaitIdle(vulkanContext->device); | ||
974 | } | ||
975 | |||
976 | destroySwapchainAndSwapchainSpecificStuff(doDestroySwapchain); | ||
977 | |||
978 | if (vulkanContext->imageAvailableSemaphore && vkDestroySemaphore) { | ||
979 | vkDestroySemaphore(vulkanContext->device, vulkanContext->imageAvailableSemaphore, NULL); | ||
980 | } | ||
981 | |||
982 | if (vulkanContext->renderingFinishedSemaphore && vkDestroySemaphore) { | ||
983 | vkDestroySemaphore(vulkanContext->device, vulkanContext->renderingFinishedSemaphore, NULL); | ||
984 | } | ||
985 | |||
986 | if (vulkanContext->device && vkDestroyDevice) { | ||
987 | vkDestroyDevice(vulkanContext->device, NULL); | ||
988 | } | ||
989 | |||
990 | if (vulkanContext->surface && vkDestroySurfaceKHR) { | ||
991 | vkDestroySurfaceKHR(vulkanContext->instance, vulkanContext->surface, NULL); | ||
992 | } | ||
993 | |||
994 | if (vulkanContext->instance && vkDestroyInstance) { | ||
995 | vkDestroyInstance(vulkanContext->instance, NULL); | ||
996 | } | ||
997 | |||
998 | SDL_free(vulkanContext->surfaceFormats); | ||
999 | } | ||
1000 | SDL_free(vulkanContexts); | ||
1001 | vulkanContexts = NULL; | ||
1002 | } | ||
1003 | |||
1004 | SDL_Vulkan_UnloadLibrary(); | ||
1005 | } | ||
1006 | |||
1007 | static bool render(void) | ||
1008 | { | ||
1009 | uint32_t frameIndex; | ||
1010 | VkResult rc; | ||
1011 | double currentTime; | ||
1012 | VkClearColorValue clearColor = { { 0 } }; | ||
1013 | VkPipelineStageFlags waitDestStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; | ||
1014 | VkSubmitInfo submitInfo = { 0 }; | ||
1015 | VkPresentInfoKHR presentInfo = { 0 }; | ||
1016 | int w, h; | ||
1017 | |||
1018 | if (!vulkanContext->swapchain) { | ||
1019 | bool result = createNewSwapchainAndSwapchainSpecificStuff(); | ||
1020 | if (!result) { | ||
1021 | SDL_Delay(100); | ||
1022 | } | ||
1023 | return result; | ||
1024 | } | ||
1025 | rc = vkAcquireNextImageKHR(vulkanContext->device, | ||
1026 | vulkanContext->swapchain, | ||
1027 | UINT64_MAX, | ||
1028 | vulkanContext->imageAvailableSemaphore, | ||
1029 | VK_NULL_HANDLE, | ||
1030 | &frameIndex); | ||
1031 | if (rc == VK_ERROR_OUT_OF_DATE_KHR) { | ||
1032 | return createNewSwapchainAndSwapchainSpecificStuff(); | ||
1033 | } | ||
1034 | |||
1035 | if ((rc != VK_SUBOPTIMAL_KHR) && (rc != VK_SUCCESS)) { | ||
1036 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
1037 | "vkAcquireNextImageKHR(): %s", | ||
1038 | getVulkanResultString(rc)); | ||
1039 | quit(2); | ||
1040 | } | ||
1041 | rc = vkWaitForFences(vulkanContext->device, 1, &vulkanContext->fences[frameIndex], VK_FALSE, UINT64_MAX); | ||
1042 | if (rc != VK_SUCCESS) { | ||
1043 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "vkWaitForFences(): %s", getVulkanResultString(rc)); | ||
1044 | quit(2); | ||
1045 | } | ||
1046 | rc = vkResetFences(vulkanContext->device, 1, &vulkanContext->fences[frameIndex]); | ||
1047 | if (rc != VK_SUCCESS) { | ||
1048 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "vkResetFences(): %s", getVulkanResultString(rc)); | ||
1049 | quit(2); | ||
1050 | } | ||
1051 | currentTime = (double)SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency(); | ||
1052 | clearColor.float32[0] = (float)(0.5 + 0.5 * SDL_sin(currentTime)); | ||
1053 | clearColor.float32[1] = (float)(0.5 + 0.5 * SDL_sin(currentTime + SDL_PI_D * 2 / 3)); | ||
1054 | clearColor.float32[2] = (float)(0.5 + 0.5 * SDL_sin(currentTime + SDL_PI_D * 4 / 3)); | ||
1055 | clearColor.float32[3] = 0.5; // for SDL_WINDOW_TRANSPARENT, ignored with VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | ||
1056 | rerecordCommandBuffer(frameIndex, &clearColor); | ||
1057 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | ||
1058 | submitInfo.waitSemaphoreCount = 1; | ||
1059 | submitInfo.pWaitSemaphores = &vulkanContext->imageAvailableSemaphore; | ||
1060 | submitInfo.pWaitDstStageMask = &waitDestStageMask; | ||
1061 | submitInfo.commandBufferCount = 1; | ||
1062 | submitInfo.pCommandBuffers = &vulkanContext->commandBuffers[frameIndex]; | ||
1063 | submitInfo.signalSemaphoreCount = 1; | ||
1064 | submitInfo.pSignalSemaphores = &vulkanContext->renderingFinishedSemaphore; | ||
1065 | rc = vkQueueSubmit(vulkanContext->graphicsQueue, 1, &submitInfo, vulkanContext->fences[frameIndex]); | ||
1066 | |||
1067 | if (rc != VK_SUCCESS) { | ||
1068 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "vkQueueSubmit(): %s", getVulkanResultString(rc)); | ||
1069 | quit(2); | ||
1070 | } | ||
1071 | presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; | ||
1072 | presentInfo.waitSemaphoreCount = 1; | ||
1073 | presentInfo.pWaitSemaphores = &vulkanContext->renderingFinishedSemaphore; | ||
1074 | presentInfo.swapchainCount = 1; | ||
1075 | presentInfo.pSwapchains = &vulkanContext->swapchain; | ||
1076 | presentInfo.pImageIndices = &frameIndex; | ||
1077 | rc = vkQueuePresentKHR(vulkanContext->presentQueue, &presentInfo); | ||
1078 | if ((rc == VK_ERROR_OUT_OF_DATE_KHR) || (rc == VK_SUBOPTIMAL_KHR)) { | ||
1079 | return createNewSwapchainAndSwapchainSpecificStuff(); | ||
1080 | } | ||
1081 | |||
1082 | if (rc != VK_SUCCESS) { | ||
1083 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, | ||
1084 | "vkQueuePresentKHR(): %s", | ||
1085 | getVulkanResultString(rc)); | ||
1086 | quit(2); | ||
1087 | } | ||
1088 | SDL_GetWindowSizeInPixels(vulkanContext->window, &w, &h); | ||
1089 | if (w != (int)vulkanContext->swapchainSize.width || h != (int)vulkanContext->swapchainSize.height) { | ||
1090 | return createNewSwapchainAndSwapchainSpecificStuff(); | ||
1091 | } | ||
1092 | return true; | ||
1093 | } | ||
1094 | |||
1095 | int main(int argc, char **argv) | ||
1096 | { | ||
1097 | int done; | ||
1098 | const SDL_DisplayMode *mode; | ||
1099 | SDL_Event event; | ||
1100 | Uint64 then, now; | ||
1101 | Uint32 frames; | ||
1102 | int dw, dh; | ||
1103 | |||
1104 | /* Initialize test framework */ | ||
1105 | state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); | ||
1106 | if (!state) { | ||
1107 | return 1; | ||
1108 | } | ||
1109 | |||
1110 | /* Set Vulkan parameters */ | ||
1111 | state->window_flags |= SDL_WINDOW_VULKAN; | ||
1112 | state->skip_renderer = 1; | ||
1113 | |||
1114 | if (!SDLTest_CommonDefaultArgs(state, argc, argv) || !SDLTest_CommonInit(state)) { | ||
1115 | SDLTest_CommonQuit(state); | ||
1116 | return 1; | ||
1117 | } | ||
1118 | |||
1119 | mode = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay()); | ||
1120 | if (mode) { | ||
1121 | SDL_Log("Screen BPP : %d", SDL_BITSPERPIXEL(mode->format)); | ||
1122 | } | ||
1123 | SDL_GetWindowSize(state->windows[0], &dw, &dh); | ||
1124 | SDL_Log("Window Size : %d,%d", dw, dh); | ||
1125 | SDL_GetWindowSizeInPixels(state->windows[0], &dw, &dh); | ||
1126 | SDL_Log("Draw Size : %d,%d", dw, dh); | ||
1127 | SDL_Log("%s", ""); | ||
1128 | |||
1129 | initVulkan(); | ||
1130 | |||
1131 | /* Main render loop */ | ||
1132 | frames = 0; | ||
1133 | then = SDL_GetTicks(); | ||
1134 | done = 0; | ||
1135 | while (!done) { | ||
1136 | /* Check for events */ | ||
1137 | frames++; | ||
1138 | while (SDL_PollEvent(&event)) { | ||
1139 | /* Need to destroy the swapchain before the window created | ||
1140 | * by SDL. | ||
1141 | */ | ||
1142 | if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { | ||
1143 | destroySwapchainAndSwapchainSpecificStuff(true); | ||
1144 | } | ||
1145 | SDLTest_CommonEvent(state, &event, &done); | ||
1146 | } | ||
1147 | |||
1148 | if (!done) { | ||
1149 | int i; | ||
1150 | for (i = 0; i < state->num_windows; ++i) { | ||
1151 | if (state->windows[i]) { | ||
1152 | vulkanContext = &vulkanContexts[i]; | ||
1153 | render(); | ||
1154 | } | ||
1155 | } | ||
1156 | } | ||
1157 | } | ||
1158 | |||
1159 | /* Print out some timing information */ | ||
1160 | now = SDL_GetTicks(); | ||
1161 | if (now > then) { | ||
1162 | SDL_Log("%2.2f frames per second", ((double)frames * 1000) / (now - then)); | ||
1163 | } | ||
1164 | |||
1165 | shutdownVulkan(true); | ||
1166 | SDLTest_CommonQuit(state); | ||
1167 | return 0; | ||
1168 | } | ||
1169 | |||
1170 | #endif | ||