diff options
| author | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
| commit | 5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch) | |
| tree | 8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/test/testvulkan.c | |
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/test/testvulkan.c')
| -rw-r--r-- | contrib/SDL-3.2.8/test/testvulkan.c | 1170 |
1 files changed, 1170 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/test/testvulkan.c b/contrib/SDL-3.2.8/test/testvulkan.c new file mode 100644 index 0000000..a728b79 --- /dev/null +++ b/contrib/SDL-3.2.8/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 | ||
