source: opengl-game/sdl-game.cpp@ 8d96e95

feature/imgui-sdl
Last change on this file since 8d96e95 was 8d96e95, checked in by Dmitry Portnoy <dportnoy@…>, 4 years ago

Avoid recreating the swap chain when the window is minimized

  • Property mode set to 100644
File size: 36.4 KB
Line 
1#include "sdl-game.hpp"
2
3#include <array>
4#include <iostream>
5#include <set>
6#include <stdexcept>
7
8#include <SDL2/SDL_vulkan.h>
9
10#include "IMGUI/imgui_impl_sdl.h"
11
12#include "logger.hpp"
13
14using namespace std;
15
16#define IMGUI_UNLIMITED_FRAME_RATE
17
18static void check_imgui_vk_result(VkResult res) {
19 if (res == VK_SUCCESS) {
20 return;
21 }
22
23 ostringstream oss;
24 oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__;
25 if (res < 0) {
26 throw runtime_error("Fatal: " + oss.str());
27 } else {
28 cerr << oss.str();
29 }
30}
31
32VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
33 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
34 VkDebugUtilsMessageTypeFlagsEXT messageType,
35 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
36 void* pUserData) {
37 cerr << "validation layer: " << pCallbackData->pMessage << endl;
38
39 // TODO: Figure out what the return value means and if it should always be VK_FALSE
40 return VK_FALSE;
41}
42
43VulkanGame::VulkanGame() {
44 // TODO: Double-check whether initialization should happen in the header, where the variables are declared, or here
45 // Also, decide whether to use this-> for all instance variables, or only when necessary
46
47 debugMessenger = VK_NULL_HANDLE;
48
49 gui = nullptr;
50 window = nullptr;
51
52 swapChainPresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR;
53 swapChainMinImageCount = 0;
54
55 shouldRecreateSwapChain = false;
56}
57
58VulkanGame::~VulkanGame() {
59}
60
61void VulkanGame::run(int width, int height, unsigned char guiFlags) {
62 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
63
64 cout << "Vulkan Game" << endl;
65
66 if (initUI(width, height, guiFlags) == RTWO_ERROR) {
67 return;
68 }
69
70 initVulkan();
71
72 // Create Descriptor Pool
73 {
74 VkDescriptorPoolSize pool_sizes[] =
75 {
76 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
77 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
78 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
79 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
80 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
81 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
82 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
83 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
84 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
85 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
86 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
87 };
88 VkDescriptorPoolCreateInfo pool_info = {};
89 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
90 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
91 pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes);
92 pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes);
93 pool_info.pPoolSizes = pool_sizes;
94
95 VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &descriptorPool),
96 "failed to create descriptor pool");
97 }
98
99 // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index
100 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
101
102 // Setup Dear ImGui context
103 IMGUI_CHECKVERSION();
104 ImGui::CreateContext();
105 ImGuiIO& io = ImGui::GetIO();
106 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
107 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
108
109 // Setup Dear ImGui style
110 ImGui::StyleColorsDark();
111 //ImGui::StyleColorsClassic();
112
113 // Setup Platform/Renderer bindings
114 ImGui_ImplSDL2_InitForVulkan(window);
115 ImGui_ImplVulkan_InitInfo init_info = {};
116 init_info.Instance = instance;
117 init_info.PhysicalDevice = physicalDevice;
118 init_info.Device = device;
119 init_info.QueueFamily = indices.graphicsFamily.value();
120 init_info.Queue = graphicsQueue;
121 init_info.DescriptorPool = descriptorPool;
122 init_info.Allocator = nullptr;
123 init_info.MinImageCount = swapChainMinImageCount;
124 init_info.ImageCount = swapChainImageCount;
125 init_info.CheckVkResultFn = check_imgui_vk_result;
126 ImGui_ImplVulkan_Init(&init_info, renderPass);
127
128 // Load Fonts
129 // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
130 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
131 // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
132 // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
133 // - Read 'docs/FONTS.md' for more instructions and details.
134 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
135 //io.Fonts->AddFontDefault();
136 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
137 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
138 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
139 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
140 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
141 //assert(font != NULL);
142
143 // Upload Fonts
144 {
145 VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool);
146
147 ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
148
149 VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue);
150
151 ImGui_ImplVulkan_DestroyFontUploadObjects();
152 }
153
154 done = false;
155 while (!done) {
156 // Poll and handle events (inputs, window resize, etc.)
157 // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
158 // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
159 // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
160 // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
161 SDL_Event event;
162 while (SDL_PollEvent(&event)) {
163 ImGui_ImplSDL2_ProcessEvent(&event);
164 if (event.type == SDL_QUIT)
165 done = true;
166 if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window))
167 done = true;
168 }
169
170 if (shouldRecreateSwapChain) {
171 gui->refreshWindowSize();
172 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
173
174 if (!isMinimized) {
175 // TODO: This should be used if the min image count changes, presumably because a new surface was created
176 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
177 // during swapchain recreation to take advantage of this
178 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
179
180 recreateSwapChain();
181
182 imageIndex = 0;
183 shouldRecreateSwapChain = false;
184 }
185 }
186
187 // Start the Dear ImGui frame
188 ImGui_ImplVulkan_NewFrame();
189 ImGui_ImplSDL2_NewFrame(window);
190 ImGui::NewFrame();
191
192 // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
193 {
194 static int counter = 0;
195
196 ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
197
198 ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
199
200 if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
201 counter++;
202 ImGui::SameLine();
203 ImGui::Text("counter = %d", counter);
204
205 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
206 ImGui::End();
207 }
208
209 ImGui::Render();
210
211 gui->refreshWindowSize();
212 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
213
214 if (!isMinimized) {
215 renderFrame(ImGui::GetDrawData());
216 presentFrame();
217 }
218 }
219
220 cleanup();
221
222 close_log();
223}
224
225bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) {
226 // TODO: Create a game-gui function to get the gui version and retrieve it that way
227
228 SDL_VERSION(&sdlVersion); // This gets the compile-time version
229 SDL_GetVersion(&sdlVersion); // This gets the runtime version
230
231 cout << "SDL " <<
232 to_string(sdlVersion.major) << "." <<
233 to_string(sdlVersion.minor) << "." <<
234 to_string(sdlVersion.patch) << endl;
235
236 // TODO: Refactor the logger api to be more flexible,
237 // esp. since gl_log() and gl_log_err() have issues printing anything besides strings
238 restart_gl_log();
239 gl_log("starting SDL\n%s.%s.%s",
240 to_string(sdlVersion.major).c_str(),
241 to_string(sdlVersion.minor).c_str(),
242 to_string(sdlVersion.patch).c_str());
243
244 // TODO: Use open_Log() and related functions instead of gl_log ones
245 // TODO: In addition, delete the gl_log functions
246 open_log();
247 get_log() << "starting SDL" << endl;
248 get_log() <<
249 (int)sdlVersion.major << "." <<
250 (int)sdlVersion.minor << "." <<
251 (int)sdlVersion.patch << endl;
252
253 // TODO: Put all fonts, textures, and images in the assets folder
254 gui = new GameGui_SDL();
255
256 if (gui->init() == RTWO_ERROR) {
257 // TODO: Also print these sorts of errors to the log
258 cout << "UI library could not be initialized!" << endl;
259 cout << gui->getError() << endl;
260 return RTWO_ERROR;
261 }
262
263 window = (SDL_Window*)gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
264 if (window == nullptr) {
265 cout << "Window could not be created!" << endl;
266 cout << gui->getError() << endl;
267 return RTWO_ERROR;
268 }
269
270 cout << "Target window size: (" << width << ", " << height << ")" << endl;
271 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
272
273 return RTWO_SUCCESS;
274}
275
276void VulkanGame::initVulkan() {
277 const vector<const char*> validationLayers = {
278 "VK_LAYER_KHRONOS_validation"
279 };
280 const vector<const char*> deviceExtensions = {
281 VK_KHR_SWAPCHAIN_EXTENSION_NAME
282 };
283
284 createVulkanInstance(validationLayers);
285 setupDebugMessenger();
286 createVulkanSurface();
287 pickPhysicalDevice(deviceExtensions);
288 createLogicalDevice(validationLayers, deviceExtensions);
289 chooseSwapChainProperties();
290 createSwapChain();
291 createImageViews();
292 createRenderPass();
293
294 createResourceCommandPool();
295 createCommandPools();
296
297 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
298 depthImage, graphicsQueue);
299
300 createFramebuffers();
301
302 // TODO: Initialize pipelines here
303
304 createCommandBuffers();
305
306 createSyncObjects();
307}
308
309void VulkanGame::cleanup() {
310 // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals)
311 //vkQueueWaitIdle(g_Queue);
312 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
313
314 ImGui_ImplVulkan_Shutdown();
315 ImGui_ImplSDL2_Shutdown();
316 ImGui::DestroyContext();
317
318 cleanupSwapChain();
319
320 // this would actually be destroyed in the pipeline class
321 vkDestroyDescriptorPool(device, descriptorPool, nullptr);
322
323 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
324
325 vkDestroyDevice(device, nullptr);
326 vkDestroySurfaceKHR(instance, surface, nullptr);
327
328 if (ENABLE_VALIDATION_LAYERS) {
329 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
330 }
331
332 vkDestroyInstance(instance, nullptr);
333
334 gui->destroyWindow();
335 gui->shutdown();
336 delete gui;
337}
338
339void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
340 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
341 throw runtime_error("validation layers requested, but not available!");
342 }
343
344 VkApplicationInfo appInfo = {};
345 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
346 appInfo.pApplicationName = "Vulkan Game";
347 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
348 appInfo.pEngineName = "No Engine";
349 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
350 appInfo.apiVersion = VK_API_VERSION_1_0;
351
352 VkInstanceCreateInfo createInfo = {};
353 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
354 createInfo.pApplicationInfo = &appInfo;
355
356 vector<const char*> extensions = gui->getRequiredExtensions();
357 if (ENABLE_VALIDATION_LAYERS) {
358 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
359 }
360
361 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
362 createInfo.ppEnabledExtensionNames = extensions.data();
363
364 cout << endl << "Extensions:" << endl;
365 for (const char* extensionName : extensions) {
366 cout << extensionName << endl;
367 }
368 cout << endl;
369
370 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
371 if (ENABLE_VALIDATION_LAYERS) {
372 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
373 createInfo.ppEnabledLayerNames = validationLayers.data();
374
375 populateDebugMessengerCreateInfo(debugCreateInfo);
376 createInfo.pNext = &debugCreateInfo;
377 }
378 else {
379 createInfo.enabledLayerCount = 0;
380
381 createInfo.pNext = nullptr;
382 }
383
384 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
385 throw runtime_error("failed to create instance!");
386 }
387}
388
389void VulkanGame::setupDebugMessenger() {
390 if (!ENABLE_VALIDATION_LAYERS) {
391 return;
392 }
393
394 VkDebugUtilsMessengerCreateInfoEXT createInfo;
395 populateDebugMessengerCreateInfo(createInfo);
396
397 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
398 throw runtime_error("failed to set up debug messenger!");
399 }
400}
401
402void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
403 createInfo = {};
404 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
405 createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
406 createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
407 createInfo.pfnUserCallback = debugCallback;
408}
409
410void VulkanGame::createVulkanSurface() {
411 if (gui->createVulkanSurface(instance, &surface) == RTWO_ERROR) {
412 throw runtime_error("failed to create window surface!");
413 }
414}
415
416void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
417 uint32_t deviceCount = 0;
418 // TODO: Check VkResult
419 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
420
421 if (deviceCount == 0) {
422 throw runtime_error("failed to find GPUs with Vulkan support!");
423 }
424
425 vector<VkPhysicalDevice> devices(deviceCount);
426 // TODO: Check VkResult
427 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
428
429 cout << endl << "Graphics cards:" << endl;
430 for (const VkPhysicalDevice& device : devices) {
431 if (isDeviceSuitable(device, deviceExtensions)) {
432 physicalDevice = device;
433 break;
434 }
435 }
436 cout << endl;
437
438 if (physicalDevice == VK_NULL_HANDLE) {
439 throw runtime_error("failed to find a suitable GPU!");
440 }
441}
442
443bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
444 VkPhysicalDeviceProperties deviceProperties;
445 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
446
447 cout << "Device: " << deviceProperties.deviceName << endl;
448
449 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
450 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
451 bool swapChainAdequate = false;
452
453 if (extensionsSupported) {
454 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
455 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
456
457 swapChainAdequate = !formats.empty() && !presentModes.empty();
458 }
459
460 VkPhysicalDeviceFeatures supportedFeatures;
461 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
462
463 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
464}
465
466void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
467 const vector<const char*>& deviceExtensions) {
468 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
469
470 if (!indices.isComplete()) {
471 throw runtime_error("failed to find required queue families!");
472 }
473
474 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
475 // using them correctly to get the most benefit out of separate queues
476
477 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
478 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
479
480 float queuePriority = 1.0f;
481 for (uint32_t queueFamily : uniqueQueueFamilies) {
482 VkDeviceQueueCreateInfo queueCreateInfo = {};
483 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
484 queueCreateInfo.queueCount = 1;
485 queueCreateInfo.queueFamilyIndex = queueFamily;
486 queueCreateInfo.pQueuePriorities = &queuePriority;
487
488 queueCreateInfoList.push_back(queueCreateInfo);
489 }
490
491 VkPhysicalDeviceFeatures deviceFeatures = {};
492 deviceFeatures.samplerAnisotropy = VK_TRUE;
493
494 VkDeviceCreateInfo createInfo = {};
495 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
496
497 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
498 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
499
500 createInfo.pEnabledFeatures = &deviceFeatures;
501
502 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
503 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
504
505 // These fields are ignored by up-to-date Vulkan implementations,
506 // but it's a good idea to set them for backwards compatibility
507 if (ENABLE_VALIDATION_LAYERS) {
508 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
509 createInfo.ppEnabledLayerNames = validationLayers.data();
510 }
511 else {
512 createInfo.enabledLayerCount = 0;
513 }
514
515 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
516 throw runtime_error("failed to create logical device!");
517 }
518
519 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
520 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
521}
522
523void VulkanGame::chooseSwapChainProperties() {
524 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
525 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
526
527 // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation
528 // Assuming that the default behavior is without setting this bit, there is no need for separate Swapchain image and image view format
529 // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40,
530 // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used.
531 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
532 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
533 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
534
535#ifdef IMGUI_UNLIMITED_FRAME_RATE
536 vector<VkPresentModeKHR> presentModes{
537 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
538 };
539#else
540 vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
541#endif
542
543 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
544
545 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
546
547 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
548
549 // If min image count was not specified, request different count of images dependent on selected present mode
550 if (swapChainMinImageCount == 0) {
551 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
552 swapChainMinImageCount = 3;
553 }
554 else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
555 swapChainMinImageCount = 2;
556 }
557 else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
558 swapChainMinImageCount = 1;
559 }
560 else {
561 throw runtime_error("unexpected present mode!");
562 }
563 }
564
565 if (swapChainMinImageCount < capabilities.minImageCount) {
566 swapChainMinImageCount = capabilities.minImageCount;
567 }
568 else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
569 swapChainMinImageCount = capabilities.maxImageCount;
570 }
571}
572
573void VulkanGame::createSwapChain() {
574 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
575
576 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
577
578 VkSwapchainCreateInfoKHR createInfo = {};
579 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
580 createInfo.surface = surface;
581 createInfo.minImageCount = swapChainMinImageCount;
582 createInfo.imageFormat = swapChainSurfaceFormat.format;
583 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
584 createInfo.imageExtent = swapChainExtent;
585 createInfo.imageArrayLayers = 1;
586 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
587
588 // TODO: Maybe save this result so I don't have to recalculate it every time
589 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
590 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
591
592 if (indices.graphicsFamily != indices.presentFamily) {
593 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
594 createInfo.queueFamilyIndexCount = 2;
595 createInfo.pQueueFamilyIndices = queueFamilyIndices;
596 }
597 else {
598 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
599 createInfo.queueFamilyIndexCount = 0;
600 createInfo.pQueueFamilyIndices = nullptr;
601 }
602
603 createInfo.preTransform = capabilities.currentTransform;
604 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
605 createInfo.presentMode = swapChainPresentMode;
606 createInfo.clipped = VK_TRUE;
607 createInfo.oldSwapchain = VK_NULL_HANDLE;
608
609 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
610 throw runtime_error("failed to create swap chain!");
611 }
612
613 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
614 throw runtime_error("failed to get swap chain image count!");
615 }
616
617 swapChainImages.resize(swapChainImageCount);
618 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
619 throw runtime_error("failed to get swap chain images!");
620 }
621}
622
623void VulkanGame::createImageViews() {
624 swapChainImageViews.resize(swapChainImageCount);
625
626 for (uint32_t i = 0; i < swapChainImageViews.size(); i++) {
627 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
628 VK_IMAGE_ASPECT_COLOR_BIT);
629 }
630}
631
632void VulkanGame::createRenderPass() {
633 VkAttachmentDescription colorAttachment = {};
634 colorAttachment.format = swapChainSurfaceFormat.format;
635 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
636 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Set to VK_ATTACHMENT_LOAD_OP_DONT_CARE to disable clearing
637 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
638 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
639 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
640 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
641 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
642
643 VkAttachmentReference colorAttachmentRef = {};
644 colorAttachmentRef.attachment = 0;
645 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
646
647 VkAttachmentDescription depthAttachment = {};
648 depthAttachment.format = findDepthFormat();
649 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
650 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
651 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
652 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
653 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
654 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
655 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
656
657 VkAttachmentReference depthAttachmentRef = {};
658 depthAttachmentRef.attachment = 1;
659 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
660
661 VkSubpassDescription subpass = {};
662 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
663 subpass.colorAttachmentCount = 1;
664 subpass.pColorAttachments = &colorAttachmentRef;
665 //subpass.pDepthStencilAttachment = &depthAttachmentRef;
666
667 VkSubpassDependency dependency = {};
668 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
669 dependency.dstSubpass = 0;
670 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
671 dependency.srcAccessMask = 0;
672 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
673 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
674
675 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
676 VkRenderPassCreateInfo renderPassInfo = {};
677 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
678 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
679 renderPassInfo.pAttachments = attachments.data();
680 renderPassInfo.subpassCount = 1;
681 renderPassInfo.pSubpasses = &subpass;
682 renderPassInfo.dependencyCount = 1;
683 renderPassInfo.pDependencies = &dependency;
684
685 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
686 throw runtime_error("failed to create render pass!");
687 }
688
689 // We do not create a pipeline by default as this is also used by examples' main.cpp,
690 // but secondary viewport in multi-viewport mode may want to create one with:
691 //ImGui_ImplVulkan_CreatePipeline(device, g_Allocator, VK_NULL_HANDLE, g_MainWindowData.RenderPass, VK_SAMPLE_COUNT_1_BIT, &g_MainWindowData.Pipeline);
692}
693
694VkFormat VulkanGame::findDepthFormat() {
695 return VulkanUtils::findSupportedFormat(
696 physicalDevice,
697 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
698 VK_IMAGE_TILING_OPTIMAL,
699 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
700 );
701}
702
703void VulkanGame::createResourceCommandPool() {
704 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
705
706 VkCommandPoolCreateInfo poolInfo = {};
707 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
708 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
709 poolInfo.flags = 0;
710
711 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
712 throw runtime_error("failed to create resource command pool!");
713 }
714}
715
716void VulkanGame::createCommandPools() {
717 commandPools.resize(swapChainImageCount);
718
719 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
720
721 for (size_t i = 0; i < swapChainImageCount; i++) {
722 VkCommandPoolCreateInfo poolInfo = {};
723 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
724 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
725 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
726
727 if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]) != VK_SUCCESS) {
728 throw runtime_error("failed to create graphics command pool!");
729 }
730 }
731}
732
733void VulkanGame::createFramebuffers() {
734 swapChainFramebuffers.resize(swapChainImageCount);
735
736 VkFramebufferCreateInfo framebufferInfo = {};
737 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
738 framebufferInfo.renderPass = renderPass;
739 framebufferInfo.width = swapChainExtent.width;
740 framebufferInfo.height = swapChainExtent.height;
741 framebufferInfo.layers = 1;
742
743 for (size_t i = 0; i < swapChainImageCount; i++) {
744 array<VkImageView, 2> attachments = {
745 swapChainImageViews[i],
746 depthImage.imageView
747 };
748
749 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
750 framebufferInfo.pAttachments = attachments.data();
751
752 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
753 throw runtime_error("failed to create framebuffer!");
754 }
755 }
756}
757
758void VulkanGame::createCommandBuffers() {
759 commandBuffers.resize(swapChainImageCount);
760
761 for (size_t i = 0; i < swapChainImageCount; i++) {
762 VkCommandBufferAllocateInfo allocInfo = {};
763 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
764 allocInfo.commandPool = commandPools[i];
765 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
766 allocInfo.commandBufferCount = 1;
767
768 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
769 throw runtime_error("failed to allocate command buffer!");
770 }
771 }
772}
773
774void VulkanGame::createSyncObjects() {
775 imageAcquiredSemaphores.resize(swapChainImageCount);
776 renderCompleteSemaphores.resize(swapChainImageCount);
777 inFlightFences.resize(swapChainImageCount);
778
779 VkSemaphoreCreateInfo semaphoreInfo = {};
780 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
781
782 VkFenceCreateInfo fenceInfo = {};
783 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
784 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
785
786 for (size_t i = 0; i < swapChainImageCount; i++) {
787 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]) != VK_SUCCESS) {
788 throw runtime_error("failed to create image acquired sempahore for a frame!");
789 }
790
791 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]) != VK_SUCCESS) {
792 throw runtime_error("failed to create render complete sempahore for a frame!");
793 }
794
795 if (vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
796 throw runtime_error("failed to create fence for a frame!");
797 }
798 }
799}
800
801void VulkanGame::renderFrame(ImDrawData* draw_data) {
802 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
803 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
804
805 if (result == VK_SUBOPTIMAL_KHR) {
806 shouldRecreateSwapChain = true;
807 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
808 shouldRecreateSwapChain = true;
809 return;
810 } else {
811 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
812 }
813
814 VKUTIL_CHECK_RESULT(
815 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
816 "failed waiting for fence!");
817
818 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
819 "failed to reset fence!");
820
821 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
822 "failed to reset command pool!");
823
824 VkCommandBufferBeginInfo beginInfo = {};
825 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
826 beginInfo.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
827
828 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
829 "failed to begin recording command buffer!");
830
831 VkRenderPassBeginInfo renderPassInfo = {};
832 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
833 renderPassInfo.renderPass = renderPass;
834 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
835 renderPassInfo.renderArea.extent = swapChainExtent;
836
837 array<VkClearValue, 2> clearValues = {};
838 clearValues[0].color = { { 0.45f, 0.55f, 0.60f, 1.00f } };
839 clearValues[1].depthStencil = { 1.0f, 0 };
840
841 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
842 renderPassInfo.pClearValues = clearValues.data();
843
844 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
845
846 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
847
848 vkCmdEndRenderPass(commandBuffers[imageIndex]);
849
850 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
851 "failed to record command buffer!");
852
853 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
854 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
855 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
856
857 VkSubmitInfo submitInfo = {};
858 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
859 submitInfo.waitSemaphoreCount = 1;
860 submitInfo.pWaitSemaphores = waitSemaphores;
861 submitInfo.pWaitDstStageMask = waitStages;
862 submitInfo.commandBufferCount = 1;
863 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
864 submitInfo.signalSemaphoreCount = 1;
865 submitInfo.pSignalSemaphores = signalSemaphores;
866
867 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
868 "failed to submit draw command buffer!");
869}
870
871void VulkanGame::presentFrame() {
872 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
873
874 VkPresentInfoKHR presentInfo = {};
875 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
876 presentInfo.waitSemaphoreCount = 1;
877 presentInfo.pWaitSemaphores = signalSemaphores;
878 presentInfo.swapchainCount = 1;
879 presentInfo.pSwapchains = &swapChain;
880 presentInfo.pImageIndices = &imageIndex;
881 presentInfo.pResults = nullptr;
882
883 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
884
885 if (result == VK_SUBOPTIMAL_KHR) {
886 shouldRecreateSwapChain = true;
887 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
888 shouldRecreateSwapChain = true;
889 return;
890 } else {
891 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
892 }
893
894 currentFrame = (currentFrame + 1) % swapChainImageCount;
895}
896
897void VulkanGame::recreateSwapChain() {
898 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
899 throw runtime_error("failed to wait for device!");
900 }
901
902 cleanupSwapChain();
903
904 createSwapChain();
905 createImageViews();
906 createRenderPass();
907
908 createCommandPools();
909
910 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
911 // and resizing the window is a common reason to recreate the swapchain
912 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
913 depthImage, graphicsQueue);
914
915 createFramebuffers();
916
917 // TODO: Update pipelines here
918
919 createCommandBuffers();
920
921 createSyncObjects();
922}
923
924void VulkanGame::cleanupSwapChain() {
925 VulkanUtils::destroyVulkanImage(device, depthImage);
926
927 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
928 vkDestroyFramebuffer(device, framebuffer, nullptr);
929 }
930
931 for (uint32_t i = 0; i < swapChainImageCount; i++) {
932 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
933 vkDestroyCommandPool(device, commandPools[i], nullptr);
934 }
935
936 for (uint32_t i = 0; i < swapChainImageCount; i++) {
937 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
938 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
939 vkDestroyFence(device, inFlightFences[i], nullptr);
940 }
941
942 vkDestroyRenderPass(device, renderPass, nullptr);
943
944 for (VkImageView imageView : swapChainImageViews) {
945 vkDestroyImageView(device, imageView, nullptr);
946 }
947
948 vkDestroySwapchainKHR(device, swapChain, nullptr);
949}
Note: See TracBrowser for help on using the repository browser.